17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 01:07:28 +01:00

Sieć trakcyjna: przęsła wg typów sieci, wysięgniki z katalogu, bramki, kotwienia i edytor sieci

Sam kod exe z gałęzi siec-trakcyjna (bez testów, notatek i danych z tests/catenary).
Dane (data/traction_*.txt) leżą poza repozytorium.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019Tqn7T8D1Gx3ptKtZgNtX5
This commit is contained in:
Claude
2026-10-23 23:15:00 +02:00
parent b4b1e6c32f
commit f30fefd91c
45 changed files with 12595 additions and 24 deletions

View File

@@ -19,6 +19,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Timer.h"
#include "world/Event.h"
#include "world/TractionPower.h"
#include "world/catenary_span.h"
#include "vehicle/Camera.h"
#include "world/mtable.h"
#include "vehicle/Train.h"
@@ -1540,6 +1541,10 @@ debug_panel::render_section_settings() const {
if( ImGui::SliderFloat( ( to_string( std::abs( Global.gfx_shadow_angle_min ), 2 ) + "###shadowanglecutoff" ).c_str(), &Global.gfx_shadow_angle_min, -1.0, -0.2f, "Shadow angle cutoff" ) ) {
Global.gfx_shadow_angle_min = quantize( Global.gfx_shadow_angle_min, 0.05f );
}
// sieć trakcyjna: zasięg rysowania przewodów, lin i uchwytów jako brył; 0 wyłącza bryły
if( ImGui::SliderFloat( ( to_string( CatenarySettings.solid_range, 0 ) + " m###catenarysolidrange" ).c_str(), &CatenarySettings.solid_range, 0.0f, 500.0f, "3D overhead wires range" ) ) {
CatenarySettings.solid_range = quantize( CatenarySettings.solid_range, 10.0f );
}
if( DebugModeFlag ) {
// sky sliders
{

View File

@@ -28,6 +28,7 @@ extern char **environ;
#include "editor/editorIncludeInfo.hpp"
#include "editor/editorGroundMesh.hpp"
#include "editor/editorFormat.hpp"
#include "world/catenary_scene.h"
#include "editor/editorGeometry.hpp"
#include "editor/editorIcons.hpp"
#include "utilities/translation.h"
@@ -1264,6 +1265,7 @@ void editor_mode::redo_one()
bool editor_mode::update()
{
// the catenary doesn't need the far tracks drawn (50 km of them cost a lot in a big scenery)
Global.editor_tracks = (current_work_area() == work_area::tracks);
selftest_step();
if (m_vehicle.leave)
@@ -1420,6 +1422,11 @@ bool editor_mode::update()
signal_move_update();
draw_signal_overlay();
}
if (m_track_tab == track_tab::catenary)
{
catenary_drag_update();
draw_catenary_overlay();
}
draw_parallel_preview();
if (m_track_tab == track_tab::lineside)
{
@@ -2901,6 +2908,23 @@ bool editor_mode::save()
if (m_profile.changed)
profile_store();
infra_store();
// the catenary (cantilevers on the poles) lives in files of its own; a file made in the editor needs the directive
std::vector<std::string> catenarynew;
if (catenary_scene_changed())
{
std::string message;
if (false == catenary_scene_save(message))
{
ui()->set_status(format(STR_C("The catenary couldn't be saved: %s"), message.c_str()), true);
return false;
}
WriteLog("Editor: catenary written: " + message, logtype::generic);
}
for (auto const &file : catenary_scene_new_files())
{
rootstatements.emplace_back("catenary " + file + " endcatenary");
catenarynew.push_back(file);
}
if (false == m_saved_camera.has_value() || glm::distance(m_saved_camera->first, glm::dvec3{Camera.Pos}) > 0.5 || glm::distance(m_saved_camera->second, glm::vec3{Camera.Angle}) > 0.01f)
{
m_saved_camera = std::make_pair(glm::dvec3{Camera.Pos}, glm::vec3{Camera.Angle});
@@ -2926,6 +2950,9 @@ bool editor_mode::save()
{
scene::Layers.terrain_directive(true);
}
if (result.success)
for (auto const &file : catenarynew)
catenary_scene_file_included(file);
ui()->set_status(result.message, false == result.success);
WriteLog("Editor: " + result.message, logtype::generic);
return result.success;
@@ -3555,6 +3582,7 @@ void editor_mode::render_environment_window()
{
Global.AirTemperature = std::clamp(temperature, -35.0f, 40.0f);
simulation::Environment.compute_weather();
catenary_scene_temperature(); // ciężary kotwień idą za temperaturą
}
ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_TextDisabled));
ImGui::TextWrapped("%s", STR_C("Seen in the editor only; the scenery keeps its own time and weather"));
@@ -4395,7 +4423,7 @@ editor_mode::work_area editor_mode::current_work_area() const
if (m_terrain_open)
return work_area::terrain;
if (m_track_window_open)
return work_area::tracks;
return m_track_tab == track_tab::catenary ? work_area::catenary : work_area::tracks;
if (m_roadtool.window)
return work_area::roads;
return work_area::surroundings;
@@ -4411,7 +4439,11 @@ void editor_mode::choose_work_area(work_area const Area)
break;
case work_area::tracks:
terrain_workspace(false);
show_track_tab(m_track_tab);
show_track_tab(m_track_tab == track_tab::catenary ? track_tab::path : m_track_tab);
break;
case work_area::catenary:
terrain_workspace(false);
show_track_tab(track_tab::catenary);
break;
case work_area::roads:
m_track_window_open = false;
@@ -4440,8 +4472,9 @@ void editor_mode::render_workspaces()
{STR_C("Tracks"), work_area::tracks, editor_icons::icon::work_tracks, STR_C("Tracks and switches, signals, objects along the track, checks of the line")},
{STR_C("Roads"), work_area::roads, editor_icons::icon::work_roads, STR_C("Roads and junctions, level crossings, the points where the traffic comes and goes")},
{STR_C("Terrain"), work_area::terrain, editor_icons::icon::work_terrain, STR_C("Heightmap terrain: chunks, sculpting, painting, water, orthophoto")},
{STR_C("Catenary"), work_area::catenary, editor_icons::icon::work_catenary, STR_C("The overhead catenary: poles with cantilevers, gantries, the wires of the chains")},
};
char const *const keys[] = {"F2", "F3", "F4", "F5"};
char const *const keys[] = {"F2", "F3", "F4", "F5", "F6"};
auto const current{current_work_area()};
auto const &style{ImGui::GetStyle()};
auto *list{ImGui::GetWindowDrawList()};
@@ -4581,7 +4614,7 @@ void editor_mode::render_shortcuts_window()
{"Ctrl+Shift+F11", "Export scenery dump"},
{"F11", "Leave the editor"},
{"F10", "Quit"}}},
{"Fields of work", {{"F2", "Surroundings"}, {"F3", "Tracks"}, {"F4", "Roads"}, {"F5", "Terrain"}}},
{"Fields of work", {{"F2", "Surroundings"}, {"F3", "Tracks"}, {"F4", "Roads"}, {"F5", "Terrain"}, {"F6", "Catenary"}}},
{"Camera",
{{"RMB held, mouse", "Look around"},
{"RMB held, W S A D", "Fly forwards, back, left, right (arrows in the legacy scheme)"},
@@ -5198,6 +5231,8 @@ void editor_mode::delete_selected()
// what's selected: a road piece or point, a signal, an include of a scenery template, a model, tracks
if (road_delete())
return;
if (m_track_tab == track_tab::catenary && ui()->mode() == nodebank_panel::TRACK && catenary_delete_selected())
return;
if (m_instance != 0 && signal_delete(m_instance))
return;
if (m_instance != 0)
@@ -5265,6 +5300,10 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
if (ui()->mode() == nodebank_panel::TRACK && is_press(Action) && (Mods & (GLFW_MOD_CONTROL | GLFW_MOD_ALT)) == 0 && typed_key(Key))
return;
// the catenary tool may have keys of its own
if (ui()->mode() == nodebank_panel::TRACK && m_track_tab == track_tab::catenary && catenary_on_key(Key, Action, Mods))
return;
// gizmo transform shortcuts (Q/W/E/R) — only when the camera isn't being flown (RMB up).
// handled before the camera keyboard step because Q/W/E are also the fly-mode movement keys,
// which would otherwise consume them.
@@ -5288,6 +5327,7 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
case GLFW_KEY_F3: choose_work_area(work_area::tracks); break;
case GLFW_KEY_F4: choose_work_area(work_area::roads); break;
case GLFW_KEY_F5: choose_work_area(work_area::terrain); break;
case GLFW_KEY_F6: choose_work_area(work_area::catenary); break;
// the edit modes in the order of the toolbar; in the track mode the digits typed as a value were taken already
case GLFW_KEY_1: choose_edit_mode(nodebank_panel::MODIFY); break;
case GLFW_KEY_2: choose_edit_mode(nodebank_panel::ADD); break;
@@ -5355,6 +5395,9 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
return;
if (Global.ctrlState && Key == GLFW_KEY_Z && is_press(Action))
{
// in the catenary tool its own changes are taken back first
if (ui()->mode() == nodebank_panel::TRACK && m_track_tab == track_tab::catenary && catenary_undo())
return;
undo_last();
return;
}
@@ -5690,6 +5733,8 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
turntable_finish_placement();
if (m_signal.placing || m_signal.moving)
signal_release();
if (catenary_dragging())
catenary_release();
}
m_dragging = false;

View File

@@ -48,6 +48,8 @@ struct vehicle_desc;
struct skin_set;
}
struct catenary_editor; // application/editormodecatenary.h
class editor_mode : public application_mode, private editor_track::observer
{
@@ -606,7 +608,8 @@ class editor_mode : public application_mode, private editor_track::observer
surroundings,
tracks,
roads,
terrain
terrain,
catenary // the catenary tool of the tracks, as a field of work of its own
};
work_area current_work_area() const;
void choose_work_area(work_area const Area);
@@ -1013,7 +1016,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, signals };
enum class track_tab { straights, route, path, turnout, profile, speed, joints, infra, lay, lineside, turntable, signals, catenary };
struct track_mode
{
char const *label;
@@ -1021,7 +1024,7 @@ class editor_mode : public application_mode, private editor_track::observer
track_tab tab;
char const *tooltip;
};
static std::array<track_mode, 12> const &track_modes();
static std::array<track_mode, 13> const &track_modes();
// the objects along the track: a tab for each tool
int m_lineside_tab{0};
void render_lineside_ui();
@@ -1078,7 +1081,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, signal_place, signal_move };
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, catenary };
kind what{kind::none};
TTrack *track{nullptr};
int path{0};
@@ -1087,6 +1090,7 @@ class editor_mode : public application_mode, private editor_track::observer
glm::dvec3 position{0.0};
editor_track::snap_target snap;
std::string action;
int detail{0}; // what the tool of the intent means by it (catenary: what was hit)
};
track_intent m_intent;
track_intent track_intent_at(int const Mods);
@@ -1489,6 +1493,28 @@ class editor_mode : public application_mode, private editor_track::observer
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;
// catenary (sieć trakcyjna): the whole tool, its state and its workings are in application/editormodecatenary.h/.cpp;
// here only the fixed hooks the rest of the editor calls, so changes of the tool rebuild only its own file
friend struct catenary_editor;
std::shared_ptr<catenary_editor> m_catenary_editor;
catenary_editor &catenary_tool();
void catenary_scan();
bool catenary_intent(track_intent &Intent); // in the catenary tab: what a click would do; false: nothing
void catenary_press(track_intent const &Intent);
void catenary_release();
bool catenary_dragging() const;
void catenary_drag_update();
void draw_catenary_overlay() const;
void render_catenary_ui();
bool catenary_delete_selected();
bool catenary_undo(); // false: nothing of its own to take back
bool catenary_on_key(int const Key, int const Action, int const Mods); // true: the key was used by the tool
std::string catenary_mode_name() const;
void catenary_key_hints(std::vector<key_hint> &Hints) const;
// the page of the catenary field of work: 0 poles, 1 gantries, 2 wires
int catenary_page();
void catenary_choose_page(int const Page);
// a model laid along the track: bent to follow it, or repeated along it
struct sweep_tool
{

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,208 @@
/*
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/.
*/
// The catenary tool of the editor (sieć trakcyjna): its state and workings. The editor (editormode.h) holds it behind a
// pointer and calls it through a few fixed hooks, so a change here rebuilds only editormodecatenary.cpp, not the files
// which include editormode.h. The tool is a friend of editor_mode and reaches the editor through `e`.
#pragma once
#include "application/editormode.h"
#include "world/catenary_suspension.h"
#include "world/catenary_cantilever.h"
#include <map>
#include <optional>
#include <string>
#include <vector>
struct catenary_editor
{
explicit catenary_editor(editor_mode &Editor) : e{Editor} {}
editor_mode &e;
using track_intent = editor_mode::track_intent;
using key_hint = editor_mode::key_hint;
// what a catenary intent (track_intent::kind::catenary) is about, in its detail
enum class intent_detail { place, select, chain, gantry, mast };
static bool is_intent(track_intent const &Intent, intent_detail const Detail)
{
return Intent.what == track_intent::kind::catenary && Intent.detail == static_cast<int>(Detail);
}
// catenary: poles with cantilevers by the track, put where the user sees them (on the orthophoto), the cantilever card
// chosen from the catalogue by the distance from the face of the pole; spans checked against the catalogue tables
struct catenary_spot
{
TTrack *track{nullptr};
int path{0};
double parameter{0.0};
glm::dvec3 axis{0.0}; // the point of the path, under the rail heads
glm::dvec2 forward{0.0, 1.0};
int side{1}; // the pole on the right (+1) or on the left (-1) of the direction of the path
double radius{0.0}; // of the track there, 0 on a straight
bool curve{false};
bool toward{true}; // the wires are offset towards the pole
double reach{0.0}; // A: from the wires to the face of the pole
int card{0};
std::string problem; // why there's no card, empty when there is one
std::string family; // the family of the card in the described catalogue; empty without it
bool execution_changed{false}; // the execution chosen in the panel has no card for this A, another one was taken
bool switch_error{false}; // a pole of a group that doesn't stand by switches (concrete), standing by one
catenary::suspension data;
};
struct catenary_change
{
int group{0}; // changes of one action are taken back together
bool chain{false}; // a change of a chain, otherwise of a suspension
std::size_t index{0};
std::optional<catenary::suspension> before; // none: the suspension was added
std::optional<catenary::chain> chain_before; // none: the chain was added
bool removed{false}; // the suspension was removed
bool gantry{false}; // a change of a gantry
std::optional<catenary::gantry> gantry_before; // none: the gantry was added
bool anchor{false}; // a change of an anchor
std::optional<catenary::anchor> anchor_before; // none: the anchor was added
};
struct catenary_tool
{
std::vector<std::string> poles;
std::vector<std::string> networks;
int pole{0};
int network{0};
int zone{1};
int execution{1};
bool swinging{true};
bool toward{true};
bool alternate{true}; // on a straight the next one goes the other way
int bend{0}; // 0: from the track, 1: straight, 2: curve
float stagger{0.30f};
float clearance{2.50f}; // from the axis of the track to the face of the pole
float height{5.60f}; // of the contact wires over the rails
float system_height{0.0f}; // hk: of the messenger over the contact wires; 0 = the card's
bool insulators25{false};
char file[128]{};
int selected{-1};
// chains: the poles placed one after another are joined by spans
bool chaining{true};
int chain{-1}; // the chain the next pole is added to
int last{-1}; // the pole the next span starts at
bool append{true}; // the next pole goes after the last one of the chain, otherwise before the first
int chain_selected{-1}; // the chain shown in the panel
int connect_from{-1}; // hanging the wires on from this suspension: the next poles clicked get the spans
char power[64]{"none"};
float voltage{3000.0f};
int material{0}; // 0 cu, 1 al
int state{0}; // 0 new, 1 worn, 2 tarnished (state 0, 2, 1 in the entry)
std::map<std::size_t, catenary_spot> spots; // where the suspensions are by the track, by their numbers
std::vector<catenary_change> undo;
int group{0};
catenary::cantilever_result probe;
bool probed{false};
bool probe_good{false};
std::string status;
bool scanned{false};
// gantries: two masts and the beam between them, drops with cantilevers on the beam
std::vector<std::string> masts;
int mast_first{0};
int mast_second{0};
bool mast_second_own{false}; // the second mast was picked by the user: it no longer follows the first
bool gantry_high{false};
int gantry_side{1};
bool gantry_placing{false}; // the next two clicks put the masts
bool gantry_single{false}; // what's placed is a single beam element on one through mast: the second click sets its direction
std::optional<glm::dvec3> gantry_first; // the first mast, waiting for the second
std::optional<glm::dvec2> gantry_track; // the direction of the track by the first mast, for the beam square to it
bool gantry_square{true}; // the beam goes square to the track
bool gantry_snap{true}; // the first mast goes at gantry_face from the axis of the track under the cursor
int shift_group{0}; // the undo group of the distance of a mast being dragged; 0: the next change opens one
float gantry_face{2.50f}; // from the axis of the track to the face of the mast (towards the track)
char gantry_skin[128]{}; // the replaceable texture of the masts, the beam and the drops
int dragging{0}; // 1: the selected pole along the track, 2: the selected gantry along the track
glm::vec2 pressed{0.f};
glm::dvec3 drag_start{0.0}; // the point of the ground the drag started at
std::vector<std::pair<std::size_t, catenary::gantry>> drag_group; // the spans of the dragged gantry as they were when the drag started
int drag_undo{0};
glm::dvec3 drag_anchor{0.0}; // the first mast of the dragged gantry where the drag started
double drag_axis{0.0}; // its axis from the axis of the track then: the drag keeps it there ("first mast from the track") // the undo group of the drag, opened at its first move
std::vector<std::pair<std::size_t, catenary::suspension>> drag_drops; // the drops of the gantry, as they were
int gantry_selected{-1}; // the gantry the drops go to
std::string drop_choice; // the drop for the next cantilevers on a gantry; empty: the shortest one which fits
// the described catalogue (data/traction_catalog.txt): the group the user picks, the family and the card in it
// (empty / 0: chosen by the track, the stagger and the distance A)
std::string catalog_kind;
std::string catalog_group;
std::string catalog_family;
int catalog_card{0};
int page{0}; // the page of the catenary field of work: 0 poles, 1 gantries, 2 wires
int gantry_hang{0}; // the next cantilevers on the selected gantry: 0 on drops of the beam, 1 straight on its masts
};
catenary_tool m_catenary;
void catenary_scan();
bool catenary_spot_at(TTrack &Track, int const Path, glm::dvec3 const &Point, glm::dvec3 const &Cursor, int const Side, catenary_spot &Spot) const;
// a pole of this type can't stand there: its group doesn't stand by switches and a switch is by it
bool catenary_switch_error(std::string const &Pole, glm::dvec3 const &Foot, TTrack const *Track) const;
bool catenary_switch_error(std::size_t const Index) const; // of a placed pole, remembered until it moves or changes type
struct switch_check
{
glm::dvec3 foot{0.0};
std::string pole;
bool error{false};
};
mutable std::vector<std::pair<TTrack const *, int>> m_switch_paths; // the paths of switches, gathered again when the number of tracks changes
mutable std::size_t m_switch_tracks{static_cast<std::size_t>(-1)};
mutable std::map<std::size_t, switch_check> m_switch_checks;
void catenary_compose(catenary_spot &Spot, double const Clearance, double const Stagger, bool const Toward, std::string const &Pole = {}) const;
bool catenary_on_mast(catenary::gantry const &Gantry, double const Stagger, catenary_spot &Spot) const;
catenary_spot const *catenary_spot_of(std::size_t const Index);
int catenary_hit() const;
int catenary_span_hit() const;
void catenary_press(track_intent const &Intent);
void catenary_select(int const Index);
void catenary_apply(catenary_spot const &Spot);
void catenary_copy(std::vector<std::size_t> const &Targets);
bool catenary_propose_toward(bool &Toward);
void catenary_place(catenary_spot const &Spot);
void catenary_chain_apply();
void catenary_connect(std::size_t const To);
int catenary_gantry_hit() const;
bool catenary_gantry_beam(std::size_t const Index, glm::dvec3 &From, glm::dvec3 &To, double &Bottom) const;
bool catenary_gantry_second(glm::dvec3 const &Point, glm::dvec3 &Second, std::string &Beam) const;
glm::dvec3 catenary_mast_point() const;
// the face of the mast from the axis of the nearest track (within 20 m); false: no track near
bool catenary_mast_face(std::string const &Mast, glm::dvec3 const &Point, double &Face, glm::dvec2 &Away) const;
// the selected gantry (all its spans and what hangs on them) moved in the plan
void catenary_gantry_shift(glm::dvec2 const &Shift);
// the place of the first mast at Axis from the axis of the track nearest to Point, on the side of Point; false: no track near
bool catenary_along_track(glm::dvec3 const &Point, double const Axis, glm::dvec3 &Place) const;
bool catenary_on_gantry(catenary_spot &Spot);
void catenary_drag_update();
// the selected gantry anew: another beam, masts, turned around its middle (degrees), or square to the nearest track
void catenary_gantry_reshape(std::string const &Beam, double const Turn, bool const Square);
void catenary_release();
void catenary_gantry_hangers(std::size_t const Gantry, std::vector<std::pair<std::size_t, catenary::suspension>> &Hangers) const;
bool catenary_delete_selected();
bool catenary_undo();
void render_catenary_ui();
bool render_catenary_catalog_ui();
void render_catenary_chain_ui();
// the anchors at both ends of the selected chain: add, settings, remove
void render_catenary_anchor_ui(std::size_t Chain);
// the anchor of the chain at its start (Start) or end; catenary_anchor_size() when none
std::size_t catenary_anchor_of(catenary::chain const &Chain, std::string const &File, bool Start) const;
void render_catenary_cantilever_ui();
void render_catenary_wires_ui();
void catenary_choose_page(int const Page);
void draw_catenary_overlay() const;
std::string catenary_intent_label(catenary_spot const &Spot) const;
// the hooks of editor_mode
bool catenary_intent(track_intent &Intent);
std::string catenary_mode_name() const;
void catenary_key_hints(std::vector<key_hint> &Hints) const;
bool catenary_on_key(int const Key, int const Action, int const Mods);
};

View File

@@ -577,9 +577,9 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve
profile_restore();
}
std::array<editor_mode::track_mode, 12> const &editor_mode::track_modes()
std::array<editor_mode::track_mode, 13> const &editor_mode::track_modes()
{
static std::array<track_mode, 12> const modes{{
static std::array<track_mode, 13> 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"},
@@ -587,6 +587,7 @@ std::array<editor_mode::track_mode, 12> const &editor_mode::track_modes()
{"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"},
{"Catenary", "N", track_tab::catenary, "Poles with cantilevers by the track, the card chosen from the catalogue by the distance of the pole; the spans checked against the catalogue"},
{"Objects", "B", track_tab::lineside, "Along the track: hectometre posts, fouling point markers, 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"},
@@ -644,6 +645,9 @@ void editor_mode::show_track_tab(track_tab const Tab)
signal_scan();
signal_refresh(true);
break;
case track_tab::catenary:
catenary_scan();
break;
default:
break;
}
@@ -719,6 +723,8 @@ std::string editor_mode::track_mode_name() const
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::catenary:
return catenary_mode_name();
case track_tab::turntable:
if (m_turntable.placing)
return STR_C("Turntable: drag to turn the bridge, release to place it");
@@ -737,6 +743,7 @@ void editor_mode::render_track_inspector()
if (false == m_track_window_open || m_track_tab != track_tab::lineside)
m_sweep.open = m_hekto.open = m_fouling.open = m_parallel.open = m_vehicle.open = false;
ui()->set_track(m_track_window_open);
ui()->set_track_title(m_track_tab == track_tab::catenary ? STR("Catenary") : std::string{});
if (false == m_track_window_open)
{
// a closed window takes its mode with it
@@ -774,9 +781,13 @@ void editor_mode::render_track_tool_options()
{
auto *track{selected_track()};
// the tools themselves are in the toolbar
render_track_search();
ImGui::Spacing();
render_track_guide();
if (m_track_tab != track_tab::catenary)
{
// the catenary is a field of work of its own: no search of the paths there
render_track_search();
ImGui::Spacing();
render_track_guide();
}
switch (m_track_tab)
{
case track_tab::lay:
@@ -804,6 +815,7 @@ void editor_mode::render_track_tool_options()
break;
case track_tab::lineside: render_lineside_ui(); break;
case track_tab::signals: render_signal_ui(); break;
case track_tab::catenary: render_catenary_ui(); break;
case track_tab::turntable: render_turntable_ui(); break;
default:
render_track_set_ui();
@@ -3515,6 +3527,10 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
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::catenary)
{
catenary_key_hints(hints);
}
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)
@@ -5053,8 +5069,11 @@ void editor_mode::draw_build_overlay() const
}
};
auto const &display{ImGui::GetIO().DisplaySize};
// the ends of all the paths; not in the catenary, where they're of no use and cost a lot in a big scenery
for (auto const *track : simulation::Paths.sequence())
{
if (m_track_tab == track_tab::catenary)
break;
if (track == nullptr || track->m_editorremoved || track->m_paths.empty())
continue;
for (auto const &path : track->m_paths)

View File

@@ -40,6 +40,7 @@ namespace
using geometry::bezier;
float const kHoverPixels{12.0f};
double const kCatenaryHoverRange{1500.0}; // m from the camera: the paths the catenary looks at under the cursor
double const kHoverLabelDelay{0.35}; // s the cursor rests on a path before its data shows up
double const kFocusDistance{25.0};
auto const kContextClick{std::chrono::milliseconds(350)};
@@ -106,6 +107,9 @@ editor_mode::track_hover editor_mode::track_under_cursor() const
for (int i = 0; i < static_cast<int>(track->m_paths.size()); ++i)
{
bezier const curve{track->m_paths[i]};
// the catenary works close to the camera: the paths far away aren't looked at (in a big scenery they cost a lot)
if (m_track_tab == track_tab::catenary && glm::distance(curve.point(0.5), glm::dvec3{Global.pCamera.Pos}) > curve.plan_length() * 0.5 + kCatenaryHoverRange)
continue;
auto const count{std::clamp(static_cast<int>(curve.plan_length() / 3.0), 6, 64)};
auto previous{curve.point(0.0)};
ImVec2 from;
@@ -181,7 +185,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 || m_signal.placing || m_signal.moving || ImGuizmo::IsUsing();
m_point_drag.active || m_handle_drag.active || m_sweep.dragging || m_turntable.placing || m_signal.placing || m_signal.moving || catenary_dragging() || ImGuizmo::IsUsing();
}
editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
@@ -220,6 +224,11 @@ 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::catenary)
{
catenary_intent(intent);
return intent;
}
if (m_track_tab == track_tab::signals)
{
if (auto const hit{signal_hit()}; hit >= 0)
@@ -453,6 +462,7 @@ void editor_mode::track_press(track_intent const &Intent)
case kind::turntable_fit: turntable_press(Intent); return;
case kind::signal_place:
case kind::signal_move: signal_press(Intent); return;
case kind::catenary: catenary_press(Intent); return;
case kind::select:
case kind::none:
{

View File

@@ -185,6 +185,15 @@ void editor_mode::work_area_tools(std::vector<tool_button> &Buttons)
[this]() { m_gauge.open = !m_gauge.open; }});
break;
}
case work_area::catenary:
{
// the pages of the catenary: poles with cantilevers, gantries, the wires of the chains
auto const page{catenary_page()};
Buttons.push_back({icon::catenary, STR_C("Poles"), nullptr, STR_C("Poles with cantilevers: LMB by a track places one, LMB on a pole selects it"), page == 0, [this]() { catenary_choose_page(0); }});
Buttons.push_back({icon::gantry, STR_C("Gantries"), nullptr, STR_C("Gantries: masts and beams, the cantilevers on their drops and masts"), page == 1, [this]() { catenary_choose_page(1); }});
Buttons.push_back({icon::wires, STR_C("Wires"), nullptr, STR_C("The chains: the type of the network, the wires, the spans between the poles"), page == 2, [this]() { catenary_choose_page(2); }});
break;
}
case work_area::roads:
{
auto &tool{m_roadtool};

View File

@@ -736,7 +736,7 @@ void editor_ui::render_tool_options()
name = STR_C("Copy to bank");
break;
case nodebank_panel::TRACK:
name = STR_C("Tracks");
name = m_tracktitle.empty() ? STR_C("Tracks") : m_tracktitle.c_str();
break;
default:
break;

View File

@@ -55,6 +55,8 @@ class editor_ui : public ui_layer
m_filloptions = std::move(Renderer);
}
// the open track window takes the mouse over, whichever toolset tab is chosen
// the title of the tool options of the tracks; empty: "Tracks"
void set_track_title(std::string Title) { m_tracktitle = std::move(Title); }
void set_track(bool const Track)
{
m_track = Track;
@@ -207,6 +209,7 @@ class editor_ui : public ui_layer
bool m_sceneopen{true};
std::function<void(scene::basic_node *, bool)> m_sceneselect;
std::function<void()> m_tracktools;
std::string m_tracktitle;
std::function<void()> m_trackselection;
int m_toolmode{-1}; // edit mode or field of work the tool options were drawn for the last time
workspace m_workspace{workspace::none};