17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-07 07:38:06 +01:00

fix(roads): an axle getting on a crossroads takes the way the other axle took, one starting where it gets on

This commit is contained in:
2026-10-22 16:22:49 +02:00
parent c7f4790aa9
commit 1cc1ba27d2
11 changed files with 543 additions and 5 deletions

View File

@@ -534,6 +534,7 @@ editor_mode::editor_mode() {
ui()->set_file_actions([this]() { save(); }, [this]() { export_scenery(); }); ui()->set_file_actions([this]() { save(); }, [this]() { export_scenery(); });
ui()->set_new_scenery([this]() { m_newscenery_asked = true; }); ui()->set_new_scenery([this]() { m_newscenery_asked = true; });
ui()->set_open_scenery([this]() { m_openscenery_asked = true; }); ui()->set_open_scenery([this]() { m_openscenery_asked = true; });
ui()->set_memcell_focus([this](scene::basic_node *Node) { start_focus(Node); });
ui()->set_edit_menu([this]() { render_edit_menu(); }); ui()->set_edit_menu([this]() { render_edit_menu(); });
ui()->set_tools_menu([this]() { render_track_presets_menu(); }); ui()->set_tools_menu([this]() { render_track_presets_menu(); });
ui()->set_menu_options([this]() { ui()->set_menu_options([this]() {

View File

@@ -20,6 +20,8 @@ http://mozilla.org/MPL/2.0/.
#include "world/Track.h" #include "world/Track.h"
#include "world/Road.h" #include "world/Road.h"
#include "world/RoadPoint.h" #include "world/RoadPoint.h"
#include "world/MemCell.h"
#include "world/Event.h"
#include "simulation/simulation.h" #include "simulation/simulation.h"
#include "imgui/imgui.h" #include "imgui/imgui.h"
@@ -537,6 +539,51 @@ bool editor_mode::render_roadpoint_layout(roadpoint_node::state &State, roadpoin
"It opens a few seconds after the last one is gone"); "It opens a few seconds after the last one is gone");
if (ImGui::Checkbox(STR_C("Paint the stop lines"), &State.stoplines)) if (ImGui::Checkbox(STR_C("Paint the stop lines"), &State.stoplines))
changed = true; changed = true;
// what the scenery closes the entry onto the crossing with
char memcell[128];
copy_text(memcell, sizeof(memcell), State.memcell);
ImGui::SetNextItemWidth(200.0f);
if (ImGui::InputTextWithHint(STR_C("Memory cell"), STR_C("(named after the crossing)"), memcell, sizeof(memcell), ImGuiInputTextFlags_EnterReturnsTrue))
{
auto name{lower_case(memcell)};
if (name == "none")
name.clear();
if (name.find(' ') == std::string::npos && name != State.memcell)
{
State.memcell = name;
changed = true;
}
}
item_tooltip("Events of the scenery close the entry onto the crossing with the first value of this memory cell:\n"
"above 0 the vehicles heading for the crossing are stopped, below 0 it's kept open whatever comes by the rails,\n"
"0 leaves it to the trains. The vehicles past the stops, on the crossing or leaving it, drive on.\n"
"The crossing sets the second value: 1 while the entry is closed, 0 while it's open.\n"
"Left empty, the cell is the one named after the crossing with \":block\" added, made if the scenery doesn't have it");
if (Point != nullptr)
{
auto *cell{Point->memcell()};
if (cell == nullptr)
{
ImGui::TextColored(warning, "%s", STR_C("The crossing has no name, so there's no memory cell to tell it by"));
}
else
{
ImGui::Text("\"%s\": %g %g", cell->name().c_str(), cell->Value1(), cell->Value2());
ImGui::SameLine();
ImGui::TextUnformatted(Point->closed() ? STR_C("The entry is closed") : STR_C("The entry is open"));
// the value can be tried out right here; the scenery isn't changed by it
int control{cell->Value1() > 0.0 ? 1 : cell->Value1() < 0.0 ? 2 : 0};
auto const before{control};
ImGui::RadioButton(STR_C("By the trains"), &control, 0);
ImGui::SameLine();
ImGui::RadioButton(STR_C("Closed"), &control, 1);
ImGui::SameLine();
ImGui::RadioButton(STR_C("Kept open"), &control, 2);
item_tooltip("Sets the first value of the memory cell, the way an event of the scenery would. It isn't saved with the scenery");
if (control != before)
cell->UpdateValues("", control == 1 ? 1.0 : control == 2 ? -1.0 : 0.0, 0.0, basic_event::flags::value1);
}
}
break; break;
} }
case roadpoint_node::kind_type::spawn: case roadpoint_node::kind_type::spawn:

View File

@@ -36,6 +36,7 @@ editor_ui::editor_ui()
add_external_panel(&m_nodebankpanel); add_external_panel(&m_nodebankpanel);
add_external_panel(&m_layerspanel); add_external_panel(&m_layerspanel);
add_external_panel(&m_includespanel); add_external_panel(&m_includespanel);
add_external_panel(&m_memcellspanel);
// the menu is a part of the layout of the editor, not a pop-up as in the driver mode // the menu is a part of the layout of the editor, not a pop-up as in the driver mode
m_menu_always = true; m_menu_always = true;
@@ -985,6 +986,14 @@ void editor_ui::render_menu_contents()
ui_layer::render_menu_contents(); ui_layer::render_menu_contents();
if (ImGui::BeginMenu(STR_C("Debug")))
{
ImGui::MenuItem(STR_C("Memory cells"), nullptr, &m_memcellspanel.is_open);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Looks up the memory cells of the scenery, shows what's in them and sets it by hand"));
ImGui::EndMenu();
}
if (m_menuoptions) if (m_menuoptions)
m_menuoptions(); m_menuoptions();
} }

View File

@@ -108,6 +108,7 @@ class editor_ui : public ui_layer
void set_file_actions(std::function<void()> Save, std::function<void()> Export); void set_file_actions(std::function<void()> Save, std::function<void()> Export);
void set_new_scenery(std::function<void()> New) { m_newscenery = std::move(New); } void set_new_scenery(std::function<void()> New) { m_newscenery = std::move(New); }
void set_open_scenery(std::function<void()> Open) { m_openscenery = std::move(Open); } void set_open_scenery(std::function<void()> Open) { m_openscenery = std::move(Open); }
void set_memcell_focus(std::function<void(scene::basic_node *)> Focus) { m_memcellspanel.focus = std::move(Focus); }
// part of the main window between the docked windows, where the 3d view is seen; for the overlays placed in it // part of the main window between the docked windows, where the 3d view is seen; for the overlays placed in it
static glm::vec2 view_min() static glm::vec2 view_min()
{ {
@@ -197,6 +198,7 @@ class editor_ui : public ui_layer
nodebank_panel m_nodebankpanel{"Node bank", true}; // templates to insert, paint or fill with nodebank_panel m_nodebankpanel{"Node bank", true}; // templates to insert, paint or fill with
layers_panel m_layerspanel{"Layers", true}; layers_panel m_layerspanel{"Layers", true};
includes_panel m_includespanel{"Include database", false}; includes_panel m_includespanel{"Include database", false};
memcells_panel m_memcellspanel{"Memory cells", false};
std::function<void()> m_save; std::function<void()> m_save;
std::function<void()> m_export; std::function<void()> m_export;
std::function<void()> m_newscenery; std::function<void()> m_newscenery;

View File

@@ -21,11 +21,13 @@ http://mozilla.org/MPL/2.0/.
#include "application/editoruilayer.h" #include "application/editoruilayer.h"
#include "rendering/renderer.h" #include "rendering/renderer.h"
#include "simulation/simulation.h" #include "simulation/simulation.h"
#include "utilities/Logs.h"
#include "editor/editorModelSets.hpp" #include "editor/editorModelSets.hpp"
#include "editor/editorFormat.hpp" #include "editor/editorFormat.hpp"
#include "editor/editorSettings.hpp" #include "editor/editorSettings.hpp"
#include "imgui/imgui_internal.h" #include "imgui/imgui_internal.h"
#include <cstring>
#include <filesystem> #include <filesystem>
#include <set> #include <set>
@@ -2453,3 +2455,197 @@ void includes_panel::render()
} }
ImGui::End(); ImGui::End();
} }
// memory cells of the scenery
void memcells_panel::render()
{
if (false == is_open)
{
return;
}
ImGui::SetNextWindowPos(ImVec2S(560, 110), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowSize(ImVec2S(620, 560), ImGuiCond_FirstUseEver);
auto const panelname{(title.empty() ? std::string{STR_C(m_name.c_str())} : title) + "###" + m_name};
if (ImGui::Begin(panelname.c_str(), &is_open, ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse))
{
render_list();
render_cell();
}
ImGui::End();
}
void memcells_panel::render_list()
{
auto const &cells{simulation::Memory.sequence()};
// the cells are never taken away, only added, so a change of the count is a change of the set
if (m_sorted.size() != static_cast<std::size_t>(std::count_if(cells.begin(), cells.end(), [](TMemCell const *Cell) { return Cell != nullptr; })))
{
m_sorted.clear();
for (auto *cell : cells)
{
if (cell != nullptr)
{
m_sorted.emplace_back(cell);
}
}
std::stable_sort(m_sorted.begin(), m_sorted.end(), [](TMemCell const *Left, TMemCell const *Right) { return Left->name() < Right->name(); });
m_listedtime = -1.0;
}
ImGui::SetNextItemWidth(260.0f);
ImGui::InputTextWithHint("##filter", STR_C("Search"), m_filter, sizeof(m_filter));
ImGui::SameLine();
ImGui::Checkbox(STR_C("In the texts too"), &m_searchtext);
ImGui::SameLine();
if (ImGui::Button(STR_C("Log all")))
{
simulation::Memory.log_all();
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", STR_C("Writes the content of all memory cells to the log"));
}
std::string filter{m_filter};
std::transform(filter.begin(), filter.end(), filter.begin(), [](unsigned char Character) { return static_cast<char>(std::tolower(Character)); });
auto const now{ImGui::GetTime()};
auto const searchtext{m_searchtext && false == filter.empty()};
if (filter != m_listedfilter || m_listedtime < 0.0 || (searchtext && now - m_listedtime > 0.5))
{
m_listedfilter = filter;
m_listedtime = now;
m_listed.clear();
auto const matches = [&filter](std::string const &Text) {
if (filter.empty())
{
return true;
}
std::string text{Text};
std::transform(text.begin(), text.end(), text.begin(), [](unsigned char Character) { return static_cast<char>(std::tolower(Character)); });
return text.find(filter) != std::string::npos;
};
for (auto *cell : m_sorted)
{
if (matches(cell->name()) || (searchtext && matches(cell->Text())))
{
m_listed.emplace_back(cell);
}
}
}
ImGui::TextDisabled(STR_C("Memory cells: %d, shown: %d"), static_cast<int>(m_sorted.size()), static_cast<int>(m_listed.size()));
auto const listheight{std::max(120.0f, ImGui::GetContentRegionAvail().y - ImGui::GetFrameHeightWithSpacing() * 7.0f)};
if (ImGui::BeginTable("##memcells", 5, ImGuiTableFlags_Resizable | ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_RowBg | ImGuiTableFlags_ScrollY, ImVec2(-1.0f, listheight)))
{
ImGui::TableSetupScrollFreeze(0, 1);
ImGui::TableSetupColumn(STR_C("Name"), ImGuiTableColumnFlags_WidthStretch, 2.0f);
ImGui::TableSetupColumn(STR_C("Text"), ImGuiTableColumnFlags_WidthStretch, 1.5f);
ImGui::TableSetupColumn(STR_C("Value 1"), ImGuiTableColumnFlags_WidthStretch, 0.7f);
ImGui::TableSetupColumn(STR_C("Value 2"), ImGuiTableColumnFlags_WidthStretch, 0.7f);
ImGui::TableSetupColumn(STR_C("Track"), ImGuiTableColumnFlags_WidthStretch, 1.0f);
ImGui::TableHeadersRow();
ImGuiListClipper clipper;
clipper.Begin(static_cast<int>(m_listed.size()));
while (clipper.Step())
{
for (int row = clipper.DisplayStart; row < clipper.DisplayEnd; ++row)
{
auto *cell{m_listed[row]};
ImGui::PushID(cell);
ImGui::TableNextRow();
ImGui::TableNextColumn();
auto const name{cell->name().empty() ? std::string{STR_C("(no name)")} : cell->name()};
if (ImGui::Selectable(name.c_str(), cell == m_selected, ImGuiSelectableFlags_SpanAllColumns | ImGuiSelectableFlags_AllowDoubleClick))
{
select(cell);
if (ImGui::IsMouseDoubleClicked(ImGuiMouseButton_Left) && focus && cell->location() != glm::dvec3{0.0})
{
focus(cell);
}
}
if (false == cell->is_exportable && ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", STR_C("Made by the simulator, it isn't saved with the scenery"));
}
ImGui::TableNextColumn();
ImGui::TextUnformatted(cell->Text().c_str());
ImGui::TableNextColumn();
ImGui::Text("%g", cell->Value1());
ImGui::TableNextColumn();
ImGui::Text("%g", cell->Value2());
ImGui::TableNextColumn();
ImGui::TextUnformatted(cell->Track != nullptr ? cell->Track->name().c_str() : cell->asTrackName.c_str());
ImGui::PopID();
}
}
ImGui::EndTable();
}
}
void memcells_panel::select(TMemCell *Cell)
{
m_selected = Cell;
if (Cell == nullptr)
{
return;
}
auto const &text{Cell->Text()};
auto const length{std::min(text.size(), sizeof(m_text) - 1)};
std::memcpy(m_text, text.data(), length);
m_text[length] = 0;
m_value1 = Cell->Value1();
m_value2 = Cell->Value2();
}
void memcells_panel::render_cell()
{
ImGui::Separator();
if (m_selected == nullptr)
{
ImGui::TextDisabled("%s", STR_C("Pick a memory cell from the list to set what's in it. A double click shows where it is"));
return;
}
auto *cell{m_selected};
auto const location{cell->location()};
ImGui::Text(STR_C("\"%s\": %s %g %g"), cell->name().c_str(), cell->Text().c_str(), cell->Value1(), cell->Value2());
ImGui::SameLine();
ImGui::BeginDisabled(false == static_cast<bool>(focus) || location == glm::dvec3{0.0});
if (ImGui::SmallButton(STR_C("Show")))
{
focus(cell);
}
ImGui::EndDisabled();
ImGui::TextDisabled(STR_C("At %.1f %.1f %.1f"), location.x, location.y, location.z);
ImGui::Checkbox("##settext", &m_settext);
ImGui::SameLine();
ImGui::SetNextItemWidth(260.0f);
ImGui::InputText(STR_C("Text"), m_text, sizeof(m_text));
ImGui::Checkbox("##setvalue1", &m_setvalue1);
ImGui::SameLine();
ImGui::SetNextItemWidth(160.0f);
ImGui::InputDouble(STR_C("Value 1"), &m_value1, 1.0, 10.0, "%g");
ImGui::Checkbox("##setvalue2", &m_setvalue2);
ImGui::SameLine();
ImGui::SetNextItemWidth(160.0f);
ImGui::InputDouble(STR_C("Value 2"), &m_value2, 1.0, 10.0, "%g");
ImGui::BeginDisabled(false == (m_settext || m_setvalue1 || m_setvalue2));
if (ImGui::Button(STR_C("Set")))
{
auto const mask{(m_settext ? basic_event::flags::text : 0) | (m_setvalue1 ? basic_event::flags::value1 : 0) | (m_setvalue2 ? basic_event::flags::value2 : 0) |
(m_add ? basic_event::flags::mode_add : 0)};
cell->UpdateValues(m_text, m_value1, m_value2, mask);
WriteLog("Memory cell \"" + cell->name() + "\" set by hand to: " + cell->Text() + " " + std::to_string(cell->Value1()) + " " + std::to_string(cell->Value2()));
}
ImGui::EndDisabled();
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
{
ImGui::SetTooltip("%s", STR_C("Puts the ticked fields in the cell, the way an UpdateValues event does. The scenery files aren't changed"));
}
ImGui::SameLine();
ImGui::Checkbox(STR_C("Add to the values"), &m_add);
ImGui::SameLine();
if (ImGui::Button(STR_C("Read again")))
{
select(cell);
}
}

View File

@@ -275,6 +275,40 @@ class includes_panel : public ui_panel
bool m_statuserror{false}; bool m_statuserror{false};
}; };
// memory cells of the scenery: looked up by name or text, with what's in them, which can be set by hand while the scenery runs
class memcells_panel : public ui_panel
{
public:
memcells_panel(std::string const &Name, bool const Isopen) : ui_panel(Name, Isopen) {}
void render() override;
// moves the camera to specified node; provided by the editor mode
std::function<void(scene::basic_node *)> focus;
private:
// methods
void render_list();
void render_cell();
void select(TMemCell *Cell);
// members
char m_filter[128]{};
bool m_searchtext{true}; // the filter is looked for in the texts of the cells as well as in their names
std::vector<TMemCell *> m_sorted; // all cells, in the order of their names
std::vector<TMemCell *> m_listed; // the ones matching the filter
std::string m_listedfilter; // filter the list was made for
double m_listedtime{-1.0}; // when it was made; the texts change, so it's made again now and then
TMemCell *m_selected{nullptr};
char m_text[256]{}; // what's going to be put in the selected cell
double m_value1{0.0};
double m_value2{0.0};
bool m_settext{true};
bool m_setvalue1{true};
bool m_setvalue2{true};
bool m_add{false}; // the values are added to what the cell holds, the way an event with the add mode does
};
class functions_panel : public ui_panel class functions_panel : public ui_panel
{ {

View File

@@ -10290,3 +10290,88 @@ msgstr "Na jakiej długości od końca p1 szyny płynnie przechodzą w szyny tor
msgid "How far from the end p2 the rails fade into those of the track joined there, where their textures differ; 0: the joint stays sharp, whatever the track joined there has" msgid "How far from the end p2 the rails fade into those of the track joined there, where their textures differ; 0: the joint stays sharp, whatever the track joined there has"
msgstr "Na jakiej długości od końca p2 szyny płynnie przechodzą w szyny toru tam dołączonego, gdy ich tekstury są różne; 0: ostra granica na styku, niezależnie od ustawienia toru tam dołączonego" msgstr "Na jakiej długości od końca p2 szyny płynnie przechodzą w szyny toru tam dołączonego, gdy ich tekstury są różne; 0: ostra granica na styku, niezależnie od ustawienia toru tam dołączonego"
# scenery editor: level crossings told by memory cells
msgid "Memory cell"
msgstr "Komórka pamięci"
msgid "(named after the crossing)"
msgstr "(nazwana od przejazdu)"
msgid "Events of the scenery close the entry onto the crossing with the first value of this memory cell:\nabove 0 the vehicles heading for the crossing are stopped, below 0 it's kept open whatever comes by the rails,\n0 leaves it to the trains. The vehicles past the stops, on the crossing or leaving it, drive on.\nThe crossing sets the second value: 1 while the entry is closed, 0 while it's open.\nLeft empty, the cell is the one named after the crossing with \":block\" added, made if the scenery doesn't have it"
msgstr "Eventy scenerii zamykają wjazd na przejazd pierwszą wartością tej komórki pamięci:\npowyżej 0 pojazdy jadące na przejazd są zatrzymywane, poniżej 0 jest on otwarty bez względu na to, co jedzie po torach,\n0 zostawia to pociągom. Pojazdy za miejscami zatrzymania, na przejeździe albo zjeżdżające z niego, jadą dalej.\nPrzejazd ustawia drugą wartość: 1, gdy wjazd jest zamknięty, 0, gdy jest otwarty.\nGdy pole jest puste, jest to komórka nazwana od przejazdu z dodanym \":block\", tworzona, jeśli sceneria jej nie ma"
msgid "The crossing has no name, so there's no memory cell to tell it by"
msgstr "Przejazd nie ma nazwy, więc nie ma komórki pamięci, którą można nim sterować"
msgid "The entry is closed"
msgstr "Wjazd zamknięty"
msgid "The entry is open"
msgstr "Wjazd otwarty"
msgid "By the trains"
msgstr "Według pociągów"
msgid "Closed"
msgstr "Zamknięty"
msgid "Kept open"
msgstr "Otwarty"
msgid "Sets the first value of the memory cell, the way an event of the scenery would. It isn't saved with the scenery"
msgstr "Ustawia pierwszą wartość komórki pamięci, tak jak zrobiłby to event scenerii. Nie jest zapisywane w scenerii"
# scenery editor: memory cells window
msgid "Debug"
msgstr "Debugowanie"
msgid "Memory cells"
msgstr "Komórki pamięci"
msgid "Looks up the memory cells of the scenery, shows what's in them and sets it by hand"
msgstr "Wyszukuje komórki pamięci scenerii, pokazuje ich zawartość i pozwala ją ustawić ręcznie"
msgid "In the texts too"
msgstr "Także w tekstach"
msgid "Log all"
msgstr "Wszystkie do logu"
msgid "Writes the content of all memory cells to the log"
msgstr "Zapisuje zawartość wszystkich komórek pamięci do logu"
msgid "Memory cells: %d, shown: %d"
msgstr "Komórek pamięci: %d, pokazanych: %d"
msgid "Text"
msgstr "Tekst"
msgid "Value 1"
msgstr "Wartość 1"
msgid "Value 2"
msgstr "Wartość 2"
msgid "Made by the simulator, it isn't saved with the scenery"
msgstr "Utworzona przez symulator, nie jest zapisywana w scenerii"
msgid "Pick a memory cell from the list to set what's in it. A double click shows where it is"
msgstr "Wybierz komórkę pamięci z listy, aby ustawić jej zawartość. Podwójne kliknięcie pokazuje, gdzie jest"
msgid "\"%s\": %s %g %g"
msgstr "\"%s\": %s %g %g"
msgid "At %.1f %.1f %.1f"
msgstr "Położenie %.1f %.1f %.1f"
msgid "Puts the ticked fields in the cell, the way an UpdateValues event does. The scenery files aren't changed"
msgstr "Wpisuje zaznaczone pola do komórki, tak jak event UpdateValues. Pliki scenerii nie są zmieniane"
msgid "Add to the values"
msgstr "Dodaj do wartości"
msgid "Read again"
msgstr "Odczytaj ponownie"

View File

@@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include "world/Track.h" #include "world/Track.h"
#include "world/Event.h" #include "world/Event.h"
#include "world/MemCell.h"
#include "world/RoadOrder.h" #include "world/RoadOrder.h"
#include "vehicle/DynObj.h" #include "vehicle/DynObj.h"
#include "vehicle/Driver.h" #include "vehicle/Driver.h"
@@ -356,6 +357,14 @@ void roadpoint_node::import(cParser &Input, glm::dvec3 const &Offset)
{ {
m_state.stoplines = (Input.getToken<std::string>() != "no"); m_state.stoplines = (Input.getToken<std::string>() != "no");
} }
else if (token == "memcell")
{
m_state.memcell = Input.getToken<std::string>();
if (m_state.memcell == "none")
{
m_state.memcell.clear();
}
}
else if (token == "interval") else if (token == "interval")
{ {
Input.getTokens(); Input.getTokens();
@@ -488,6 +497,7 @@ void roadpoint_node::bind()
{ {
case kind_type::crossing: case kind_type::crossing:
{ {
bind_memcell();
bind_crossing(); bind_crossing();
break; break;
} }
@@ -574,6 +584,42 @@ void roadpoint_node::bind()
} }
} }
// name of the memory cell the crossing is told by
std::string roadpoint_node::memcell_name() const
{
if (false == m_state.memcell.empty())
{
return m_state.memcell;
}
return m_name.empty() || m_name == "none" ? std::string{} : m_name + ":block";
}
// finds the memory cell the crossing is told by, or makes it
void roadpoint_node::bind_memcell()
{
auto const name{memcell_name()};
if (name.empty())
{
m_memcell = nullptr;
return;
}
if (m_memcell != nullptr && m_memcell->name() == name)
{
return;
}
m_memcell = simulation::Memory.find(name);
if (m_memcell == nullptr)
{
scene::node_data nodedata;
nodedata.name = name;
m_memcell = new TMemCell(nodedata);
// NOTE: it's made anew each time the scenery is loaded, so it isn't saved with it
m_memcell->is_exportable = false;
m_memcell->location(m_state.position);
simulation::Memory.insert(m_memcell);
}
}
// finds the rails of a crossing, and the places the vehicles of each lane are to stop at // finds the rails of a crossing, and the places the vehicles of each lane are to stop at
void roadpoint_node::bind_crossing() void roadpoint_node::bind_crossing()
{ {
@@ -896,9 +942,12 @@ void roadpoint_node::update_crossing(double const Deltatime)
} }
m_seen.swap(seen); m_seen.swap(seen);
} }
if ((m_hold > 0.0) != m_closed) // the scenery can close the entry, or keep it open, whatever the trains do
auto const control{m_memcell != nullptr ? m_memcell->Value1() : 0.0};
auto const closed{control > 0.0 || (control == 0.0 && m_hold > 0.0)};
if (closed != m_closed)
{ {
close(m_hold > 0.0); close(closed);
} }
} }
@@ -906,10 +955,15 @@ void roadpoint_node::update_crossing(double const Deltatime)
void roadpoint_node::close(bool const Closed) void roadpoint_node::close(bool const Closed)
{ {
m_closed = Closed; m_closed = Closed;
// only the vehicles heading for the crossing are stopped, at the stops ahead of it; the ones past them leave it
for (std::size_t idx = 0; idx < m_stops.size() && idx < m_events.size(); ++idx) for (std::size_t idx = 0; idx < m_stops.size() && idx < m_events.size(); ++idx)
{ {
static_cast<stop_event *>(m_events[idx])->m_velocity = (Closed ? 0.0 : -1.0); static_cast<stop_event *>(m_events[idx])->m_velocity = (Closed ? 0.0 : -1.0);
} }
if (m_memcell != nullptr && m_memcell->Value2() != (Closed ? 1.0 : 0.0))
{
m_memcell->UpdateValues("", 0.0, Closed ? 1.0 : 0.0, basic_event::flags::value2);
}
} }
// puts a waiting vehicle on the road when its time comes // puts a waiting vehicle on the road when its time comes
@@ -1150,6 +1204,10 @@ void roadpoint_node::export_as_text_(std::ostream &Output) const
case kind_type::crossing: case kind_type::crossing:
{ {
Output << "clearance " << m_state.clearance << ' ' << "warning " << m_state.warning << ' ' << "stoplines " << (m_state.stoplines ? "yes" : "no") << ' '; Output << "clearance " << m_state.clearance << ' ' << "warning " << m_state.warning << ' ' << "stoplines " << (m_state.stoplines ? "yes" : "no") << ' ';
if (false == m_state.memcell.empty())
{
Output << "memcell " << m_state.memcell << ' ';
}
break; break;
} }
case kind_type::spawn: case kind_type::spawn:

View File

@@ -25,19 +25,23 @@ class TDynamicObject;
class basic_event; class basic_event;
// a place on a road where something happens to the traffic, or is painted for it: // a place on a road where something happens to the traffic, or is painted for it:
// - a level crossing: vehicles are stopped ahead of the rails for as long as a train is near, // - a level crossing: vehicles are stopped ahead of the rails for as long as a train is near, or the scenery says so,
// - a point where vehicles appear, drawn from a set made ready while the scenery is loaded, // - a point where vehicles appear, drawn from a set made ready while the scenery is loaded,
// - a point where vehicles are taken away. the ones which came from a point of the previous kind go back to its set, // - a point where vehicles are taken away. the ones which came from a point of the previous kind go back to its set,
// - a pedestrian crossing: stripes painted across the road. // - a pedestrian crossing: stripes painted across the road.
// scenery entries: // scenery entries:
// node <max> <min> <name> crossing <position> [clearance <m>] [warning <m>] [stoplines <yes|no>] endcrossing // node <max> <min> <name> crossing <position> [clearance <m>] [warning <m>] [stoplines <yes|no>] [memcell <name>] endcrossing
// node <max> <min> <name> spawn <position> [interval <s>] [variation <0..1>] [velocity <km/h>] [count <n>] // node <max> <min> <name> spawn <position> [interval <s>] [variation <0..1>] [velocity <km/h>] [count <n>]
// [node <max> <min> <name> dynamic <folder> <skin> <type> <path> <offset> <driver> <velocity> <load> [<load type>] enddynamic]... endspawn // [node <max> <min> <name> dynamic <folder> <skin> <type> <path> <offset> <driver> <velocity> <load> [<load type>] enddynamic]... endspawn
// node <max> <min> <name> despawn <position> [radius <m>] enddespawn // node <max> <min> <name> despawn <position> [radius <m>] enddespawn
// node <max> <min> <name> crosswalk <position> [length <m>] endcrosswalk // node <max> <min> <name> crosswalk <position> [length <m>] endcrosswalk
// the vehicles of a spawn point are given the way vehicles are put in a scenery; where they're put and how they're driven // the vehicles of a spawn point are given the way vehicles are put in a scenery; where they're put and how they're driven
// is up to the point, so the path, the offset, the driver and the velocity of these entries don't matter. the listed vehicles // is up to the point, so the path, the offset, the driver and the velocity of these entries don't matter. the listed vehicles
// are what the point draws from to have <count> vehicles of its own on the roads; one which gets stuck is taken off the road // are what the point draws from to have <count> vehicles of its own on the roads; one which gets stuck is taken off the road.
// a level crossing is told by the scenery through a memory cell: the one named in its entry, or "<name of the crossing>:block",
// made if the scenery doesn't have it. its first value above zero closes the entry onto the crossing, below zero keeps it open
// whatever comes by the rails, and zero leaves it to the trains. the vehicles past the stops, on the crossing or leaving it, drive on.
// the second value is set by the crossing: 1 while its entry is closed, 0 while it's open
class roadpoint_node : public scene::basic_node class roadpoint_node : public scene::basic_node
{ {
@@ -71,6 +75,7 @@ class roadpoint_node : public scene::basic_node
float clearance{6.f}; // distance along the road from the point to the places the vehicles stop at float clearance{6.f}; // distance along the road from the point to the places the vehicles stop at
float warning{800.f}; // how far along the rails a train coming closes the crossing float warning{800.f}; // how far along the rails a train coming closes the crossing
bool stoplines{true}; // lines are painted where the vehicles stop bool stoplines{true}; // lines are painted where the vehicles stop
std::string memcell; // name of the memory cell the scenery tells the crossing by; empty: the one named after the crossing
// point where vehicles appear // point where vehicles appear
float interval{20.f}; // seconds from a vehicle to the next float interval{20.f}; // seconds from a vehicle to the next
float variation{0.5f}; // part of the interval which is drawn at random float variation{0.5f}; // part of the interval which is drawn at random
@@ -135,6 +140,13 @@ class roadpoint_node : public scene::basic_node
{ {
return m_rails.size(); return m_rails.size();
} }
// memory cell the level crossing is told by, nullptr if there's none
TMemCell *memcell() const
{
return m_memcell;
}
// name of that cell: the one the entry gives, or the one made of the name of the crossing; empty if neither is there
std::string memcell_name() const;
// vehicles made for a spawn point, and how many of them wait for their turn // vehicles made for a spawn point, and how many of them wait for their turn
std::size_t vehicles() const std::size_t vehicles() const
{ {
@@ -169,6 +181,7 @@ class roadpoint_node : public scene::basic_node
void deserialize_(std::istream &Input) override; void deserialize_(std::istream &Input) override;
void export_as_text_(std::ostream &Output) const override; void export_as_text_(std::ostream &Output) const override;
void bind_crossing(); void bind_crossing();
void bind_memcell();
void update_crossing(double const Deltatime); void update_crossing(double const Deltatime);
void update_spawn(double const Deltatime); void update_spawn(double const Deltatime);
void update_despawn(); void update_despawn();
@@ -184,6 +197,7 @@ class roadpoint_node : public scene::basic_node
std::map<TDynamicObject const *, double> m_seen; // rail vehicles near, with how far each was the last time std::map<TDynamicObject const *, double> m_seen; // rail vehicles near, with how far each was the last time
bool m_closed{false}; bool m_closed{false};
double m_hold{0.0}; // time left for the crossing to stay closed after the last train was seen double m_hold{0.0}; // time left for the crossing to stay closed after the last train was seen
TMemCell *m_memcell{nullptr}; // what the scenery tells the crossing by
double m_scan{0.0}; // time left to the next look at the rails double m_scan{0.0}; // time left to the next look at the rails
// spawn and despawn // spawn and despawn
std::vector<TTrack *> m_lanes; // spawn: the lane the vehicles are put on. despawn: lanes the vehicles are taken from std::vector<TTrack *> m_lanes; // spawn: the lane the vehicles are put on. despawn: lanes the vehicles are taken from

View File

@@ -22,6 +22,31 @@ http://mozilla.org/MPL/2.0/.
#include "vehicle/Driver.h" #include "vehicle/Driver.h"
#include "utilities/Logs.h" #include "utilities/Logs.h"
namespace {
// how far the start of a way across a crossroads can be from the end of the path an axle leaves, to be where the axle gets on
constexpr double cross_reach { 1.0 };
// number of the ways across a crossroads; each can be taken either way
int cross_count( TTrack const &Cross ) {
return Cross.SwitchExtension->iRoads == 4 ? 6 : 3;
}
// where the way of specified code starts, and the direction it leads in there. the code is the number of the way, 1..6,
// negative if the way is taken against its points
bool cross_start( TTrack const &Cross, int const Code, glm::dvec3 &Point, glm::dvec3 &Direction ) {
if( Code == 0 || std::abs( Code ) > cross_count( Cross ) ) { return false; }
auto const &segment { Cross.SwitchExtension->Segments[ std::abs( Code ) - 1 ] };
if( segment == nullptr ) { return false; }
Point = ( Code > 0 ? segment->FastGetPoint_0() : segment->FastGetPoint_1() );
Direction = ( Code > 0 ? segment->FastGetPoint( 0.05 ) : segment->FastGetPoint( 0.95 ) ) - Point;
return true;
}
} // namespace
TTrackFollower::~TTrackFollower() TTrackFollower::~TTrackFollower()
{ {
} }
@@ -30,6 +55,8 @@ bool TTrackFollower::Init(TTrack *pTrack, TDynamicObject *NewOwner, double fDir)
{ {
fDirection = fDir; fDirection = fDir;
Owner = NewOwner; Owner = NewOwner;
// the axle is put on the track, it doesn't come from the one it was on before
pCurrentSegment = nullptr;
SetCurrentTrack(pTrack, 0); SetCurrentTrack(pTrack, 0);
iEventFlag = 3; // na torze startowym również wykonać eventy 1/2 iEventFlag = 3; // na torze startowym również wykonać eventy 1/2
iEventallFlag = 3; iEventallFlag = 3;
@@ -92,6 +119,9 @@ TTrack * TTrackFollower::SetCurrentTrack(TTrack *pTrack, int end)
// kiedy powstaje! // kiedy powstaje!
iSegment = 1; // doraźna poprawka iSegment = 1; // doraźna poprawka
} }
// the way has to start where the axle gets on. the one the driver picked may not: the driver may have looked
// again since the other axle got on, the vehicle may have no driver of its own, or it may be going backwards
iSegment = CrossEntry( *pTrack, iSegment, end );
if ((end ? iSegment : -iSegment) < 0) if ((end ? iSegment : -iSegment) < 0)
fDirection = -fDirection; // wtórna zmiana fDirection = -fDirection; // wtórna zmiana
pTrack->SwitchForced(abs(iSegment) - 1, nullptr); // wybór zapamiętanego segmentu pTrack->SwitchForced(abs(iSegment) - 1, nullptr); // wybór zapamiętanego segmentu
@@ -123,6 +153,67 @@ TTrack * TTrackFollower::SetCurrentTrack(TTrack *pTrack, int end)
return pCurrentTrack; return pCurrentTrack;
}; };
// picks the way across the crossroads (Cross) for the axle which gets on it by specified end of the path it's on. it's the way
// taken by another axle of the vehicle, or of a vehicle coupled to it, which is on the crossroads already; or the one the driver
// picked (Wish); or, if neither starts where the axle gets on, the one leading the most straight on of the ways which start there
int TTrackFollower::CrossEntry( TTrack const &Cross, int const Wish, int const End ) const {
if( pCurrentSegment == nullptr || Cross.SwitchExtension == nullptr ) { return Wish; }
auto const joint { End ? pCurrentSegment->FastGetPoint_1() : pCurrentSegment->FastGetPoint_0() };
auto const starts_here = [&]( int const Code ) {
glm::dvec3 point, direction;
return cross_start( Cross, Code, point, direction ) && glm::distance( point, joint ) < cross_reach; };
if( Owner != nullptr ) {
for( auto const *vehicle : { Owner, Owner->Prev(), Owner->Next() } ) {
if( vehicle == nullptr ) { continue; }
for( auto const *axle : { &vehicle->Axle0, &vehicle->Axle1 } ) {
if( axle == this || axle->pCurrentTrack != &Cross || axle->iSegment == 0 ) { continue; }
if( starts_here( axle->iSegment ) ) { return axle->iSegment; }
// a vehicle going back over the crossroads gets on the way at its other end
if( starts_here( -axle->iSegment ) ) { return -axle->iSegment; }
}
}
}
if( starts_here( Wish ) ) { return Wish; }
auto const incoming { joint - ( End ? pCurrentSegment->FastGetPoint( 0.95 ) : pCurrentSegment->FastGetPoint( 0.05 ) ) };
auto nearest { cross_reach };
for( int way = 1; way <= cross_count( Cross ); ++way ) {
for( auto const code : { way, -way } ) {
glm::dvec3 point, direction;
if( cross_start( Cross, code, point, direction ) ) {
nearest = std::min( nearest, glm::distance( point, joint ) );
}
}
}
if( nearest >= cross_reach ) {
// no way starts there, the crossroads isn't joined with the path; it's left the way it was
return Wish;
}
auto result { Wish };
auto straightest { -2.0 };
for( int way = 1; way <= cross_count( Cross ); ++way ) {
for( auto const code : { way, -way } ) {
glm::dvec3 point, direction;
if( false == cross_start( Cross, code, point, direction )
|| glm::distance( point, joint ) > nearest + 0.05 ) {
continue;
}
auto const straight {
glm::length2( incoming ) > 0.0 && glm::length2( direction ) > 0.0 ?
glm::dot( glm::normalize( incoming ), glm::normalize( direction ) ) :
-1.0 };
if( straight > straightest ) {
straightest = straight;
result = code;
}
}
}
return result;
}
bool TTrackFollower::Move(double fDistance, bool bPrimary) bool TTrackFollower::Move(double fDistance, bool bPrimary)
{ // przesuwanie wózka po torach o odległość (fDistance), z wyzwoleniem eventów { // przesuwanie wózka po torach o odległość (fDistance), z wyzwoleniem eventów
// bPrimary=true - jest pierwszą osią w pojeździe, czyli generuje eventy i przepisuje pojazd // bPrimary=true - jest pierwszą osią w pojeździe, czyli generuje eventy i przepisuje pojazd

View File

@@ -50,6 +50,7 @@ public:
// methods // methods
bool ComputatePosition(); // przeliczenie pozycji na torze bool ComputatePosition(); // przeliczenie pozycji na torze
int JoinedEnd( int const End ) const; int JoinedEnd( int const End ) const;
int CrossEntry( TTrack const &Cross, int const Wish, int const End ) const;
// members // members
TTrack *pCurrentTrack = nullptr; // na którym torze siê znajduje TTrack *pCurrentTrack = nullptr; // na którym torze siê znajduje
std::shared_ptr<TSegment> pCurrentSegment; // zwrotnice mog¹ mieæ dwa segmenty std::shared_ptr<TSegment> pCurrentSegment; // zwrotnice mog¹ mieæ dwa segmenty