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

feat(editor): every switch laid in the editor gets the key frogs, a box in the Switch panel sets or clears it as one undo step; the editor rebuilds the tracks crossing a changed or removed one; self-test commands frog, crossings, frogkey and geomhash; Polish translation

This commit is contained in:
maj00r
2026-10-13 18:30:16 +02:00
parent 16672e81cc
commit a93ed67c58
5 changed files with 196 additions and 2 deletions

View File

@@ -44,6 +44,10 @@ http://mozilla.org/MPL/2.0/.
// wait <frames>
// camera <x> <y> <z> <x> <y> <z> from the first point, looking at the second
// select <track name>
// frog [name] logs where the inner rails of each switch cross: distance on both paths, 1:n, throat, nose, check rails
// crossings [name] logs where rails of tracks cross rails of other tracks, checks that each acute crossing has one partner and one nose
// frogkey name [on|off] sets or clears the frogs key of a switch (one undo step); logs the key, whether it is written, the frog and the crossings
// geomhash [name] logs the number of vertices and a hash of the drawn geometry of each rail track (rails, trackbed, cut faces), to compare builds
// sweep <key>=<value>... model, params (separated by commas), lateral, height, side (origin, near, far,
// centre), top (origin, bottom, top, edge), mode (bend, repeat), step,
// length (original, all, or metres), from, to, widen (0, 1), face (0, 1), tilt (0, 1)
@@ -142,6 +146,154 @@ void editor_mode::selftest_step()
ui()->set_node(m_node);
WriteLog("SELFTEST select " + name + (m_node != nullptr ? "" : ": no such track"));
}
else if (command == "frog")
{
std::string name;
words >> name;
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->SwitchExtension == nullptr)
continue;
if (false == name.empty() && track->name() != name)
continue;
auto const frog{track->frog()};
if (false == frog.valid)
{
WriteLog(format("SELFTEST frog %s: no frog, paths %.3f and %.3f m, blades %.3f m", track->name().c_str(), track->SwitchExtension->Segments[0]->GetLength(), track->SwitchExtension->Segments[1]->GetLength(), frog.blades[0]));
continue;
}
auto const n{1.0 / std::tan(frog.angle)};
glm::dvec3 point, across;
track->SwitchExtension->Segments[0]->plan_frame(frog.at[0], point, across);
auto const gauge{point + across * (frog.side[0] * 0.5 * std::abs(track->fTrackWidth))};
auto const check{0.5 * std::clamp(0.5 * n, 3.6, 9.0)};
WriteLog(format("SELFTEST frog %s: crossing at %.3f / %.3f m (%.3f %.3f %.3f), 1:%.2f, side %d, blades %.3f / %.3f m, throat %.3f, nose %.3f, wing end %.3f, check rails %.3f-%.3f / %.3f-%.3f m", track->name().c_str(),
frog.at[0], frog.at[1], gauge.x, gauge.y, gauge.z, n, frog.side[0], frog.blades[0], frog.blades[1], frog.at[0] - 0.045 * n, frog.at[0] + 0.012 * n, frog.at[0] + 0.15 * n,
frog.at[0] - 0.25 - check, frog.at[0] - 0.25 + check, frog.at[1] - 0.25 - check, frog.at[1] - 0.25 + check));
}
}
else if (command == "geomhash")
{
std::string name;
words >> name;
auto const mix = [](std::uint64_t &Hash, float const Value) {
Hash = (Hash ^ static_cast<std::uint64_t>(static_cast<std::int64_t>(std::llround(Value * 1e4)))) * 1099511628211ull;
};
std::uint64_t total{1469598103934665603ull};
std::size_t vertices{0};
int tracks{0};
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || (track->iCategoryFlag & 15) != 1)
continue;
if (false == name.empty() && track->name() != name)
continue;
std::uint64_t hash{1469598103934665603ull};
std::size_t count{0};
int chunks{0};
for (auto const *sequence : {&track->Geometry1, &track->Geometry2, &track->GeometryCut1, &track->GeometryCut2})
{
for (auto const &geometry : *sequence)
{
++chunks;
for (auto const &vertex : GfxRenderer->Vertices(geometry))
{
for (int i = 0; i < 3; ++i)
{
mix(hash, vertex.position[i]);
mix(hash, vertex.normal[i]);
}
mix(hash, vertex.texture.x);
mix(hash, vertex.texture.y);
++count;
}
}
mix(hash, -1.f);
}
WriteLog(format("SELFTEST geom %s: chunks %d, vertices %zu, hash %016llx", track->name().c_str(), chunks, count, static_cast<unsigned long long>(hash)));
total = (total ^ hash) * 1099511628211ull;
vertices += count;
++tracks;
}
WriteLog(format("SELFTEST geom total: tracks %d, vertices %zu, hash %016llx", tracks, vertices, static_cast<unsigned long long>(total)));
}
else if (command == "frogkey")
{
std::string name, value;
words >> name >> value;
auto *track{simulation::Paths.find(name)};
if (track == nullptr || track->eType != tt_Switch || track->SwitchExtension == nullptr)
WriteLog(format("SELFTEST frogkey %s: no such switch", name.c_str()));
else
{
if (value == "on" || value == "off")
{
auto const before{editor_track::capture(*track)};
track->SwitchExtension->m_frogs = (value == "on");
push_track_snapshot({{track, before}});
editor_track::commit_parameters(*track);
}
std::ostringstream text;
track->export_as_text(text);
WriteLog(format("SELFTEST frogkey %s: key %d, written %d, frog %d, crossings %zu, profile '%s'", name.c_str(), track->SwitchExtension->m_frogs ? 1 : 0, text.str().find(" frogs ") != std::string::npos ? 1 : 0,
track->frog().valid ? 1 : 0, track->rail_crossings().size(), track->m_profile1.first.c_str()));
}
}
else if (command == "crossings")
{
std::string name;
words >> name;
auto const started{std::chrono::steady_clock::now()};
int tracks{0};
int found{0};
int acutes{0};
int mismatched{0};
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || (track->iCategoryFlag & 15) != 1)
continue;
if (false == name.empty() && track->name() != name)
continue;
++tracks;
for (auto const &crossing : track->rail_crossings())
{
++found;
auto const kind{crossing.angle > 0.785 ? "wide" : crossing.facing < 0.0 ? "acute" : "obtuse"};
WriteLog(format("SELFTEST crossing %s path %d side %d at %.3f m: %s with %s, 1:%.2f, into %.3f, facing %.3f, nose %d", track->name().c_str(), crossing.path, crossing.side, crossing.at, kind,
crossing.track->name().c_str(), 1.0 / std::tan(crossing.angle), crossing.into, crossing.facing, crossing.nose ? 1 : 0));
}
auto const gauge = [](TTrack *Track, TTrack::rail_crossing const &Crossing) {
auto const &segment{Track->eType == tt_Switch ? *Track->SwitchExtension->Segments[Crossing.path] : *Track->Segment};
glm::dvec3 point, across;
segment.plan_frame(Crossing.at, point, across);
return point + across * (Crossing.side * 0.5 * std::abs(Track->fTrackWidth));
};
for (auto const &crossing : track->rail_crossings(false))
{
if (crossing.angle > 0.785 || crossing.facing >= 0.0)
continue;
++acutes;
auto const here{gauge(track, crossing)};
auto partners{0};
auto noses{crossing.nose ? 1 : 0};
for (auto const &other : crossing.track->rail_crossings(false))
{
if (other.track != track || other.angle > 0.785 || other.facing >= 0.0)
continue;
auto const there{gauge(crossing.track, other)};
if (glm::length(glm::dvec2{there.x - here.x, there.z - here.z}) > 0.05)
continue;
++partners;
noses += other.nose ? 1 : 0;
}
if (partners != 1 || noses != 1)
++mismatched;
WriteLog(format("SELFTEST acute %s %s path %d side %d at %.3f m with %s (%.2f %.2f %.2f): partners %d, noses %d", partners != 1 || noses != 1 ? "mismatch" : "ok", track->name().c_str(), crossing.path, crossing.side, crossing.at, crossing.track->name().c_str(), here.x, here.y, here.z, partners, noses));
}
}
auto const elapsed{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - started).count()};
WriteLog(format("SELFTEST crossings: %d rail crossings on %d tracks, %d acute, %d mismatched, %.1f ms", found, tracks, acutes, mismatched, elapsed));
}
else if (command == "sweep")
{
std::map<std::string, std::string> values;

View File

@@ -1005,6 +1005,16 @@ void editor_mode::render_turnout_ui()
if (m_switch.drive >= 0 && ImGui::Button(STR_C("Put the drive by the switch")))
place_switch_drive(*track);
}
bool frogs{track->SwitchExtension->m_frogs};
if (ImGui::Checkbox(STR_C("Frog and check rails made from the rails"), &frogs))
{
auto const before{editor_track::capture(*track)};
track->SwitchExtension->m_frogs = frogs;
push_track_snapshot({{track, before}});
editor_track::commit_parameters(*track);
}
item_tooltip("Cuts the rails of the switch at its frog and lays wing rails and check rails, for rail profiles S60, UIC60 and S39.\n"
"Off for a switch whose include has its own model of them");
if (false == m_switch.status.empty())
ImGui::TextWrapped("%s", m_switch.status.c_str());
if (angle > 1e-6)

View File

@@ -145,6 +145,7 @@ editor_track::state editor_track::capture(TTrack const &Track)
result.material1 = Track.m_material1;
result.material2 = Track.m_material2;
result.material3 = Track.SwitchExtension ? Track.SwitchExtension->m_material3 : null_handle;
result.frogs = Track.SwitchExtension && Track.SwitchExtension->m_frogs;
result.texlength = Track.fTexLength;
result.texheight = texture_height(Track);
result.texwidth = Track.fTexWidth;
@@ -234,6 +235,7 @@ void editor_track::apply(TTrack &Track, state const &State)
{
Track.SwitchExtension->fVelocity = State.switchvelocity;
Track.SwitchExtension->m_material3 = State.material3;
Track.SwitchExtension->m_frogs = State.frogs;
}
Track.fTrackWidth = State.width;
Track.fTrackWidth2 = State.width;
@@ -2404,6 +2406,7 @@ TTrack *editor_track::create_switch(switch_template const &Template, std::vector
write_ending(text, Style);
track = load_path(text.str(), Style, Name);
}
track->SwitchExtension->m_frogs = true;
track->m_paths.assign(Paths.begin(), Paths.begin() + 2);
return track;
}
@@ -2820,12 +2823,34 @@ void editor_track::retire(TTrack &Track)
update_transition(*neighbour);
rebuild_geometry(*neighbour);
}
auto crossings{Track.m_crossings};
for (auto *crossing : Track.m_crossings)
for (auto *neighbour : neighbours(*crossing))
if (std::find(crossings.begin(), crossings.end(), neighbour) == crossings.end())
crossings.push_back(neighbour);
for (auto *crossing : crossings)
if (crossing != &Track && false == crossing->m_editorremoved)
rebuild_geometry(*crossing);
if (s_observer != nullptr)
s_observer->track_retired(Track);
}
void editor_track::rebuild_geometry(TTrack &Track)
{
auto &section{simulation::Region->section(Track.location())};
Track.rebuild_geometry(section.m_geometrycreated ? section.m_geometrybank : gfx::geometrybank_handle{});
auto const rebuild = [](TTrack &Path) {
auto &section{simulation::Region->section(Path.location())};
Path.rebuild_geometry(section.m_geometrycreated ? section.m_geometrybank : gfx::geometrybank_handle{});
};
auto crossings{Track.m_crossings};
rebuild(Track);
for (auto *crossing : Track.m_crossings)
if (std::find(crossings.begin(), crossings.end(), crossing) == crossings.end())
crossings.push_back(crossing);
for (std::size_t i = 0, count = crossings.size(); i < count; ++i)
for (auto *neighbour : neighbours(*crossings[i]))
if (std::find(crossings.begin(), crossings.end(), neighbour) == crossings.end())
crossings.push_back(neighbour);
for (auto *crossing : crossings)
if (crossing != &Track && false == crossing->m_editorremoved)
rebuild(*crossing);
}

View File

@@ -49,6 +49,7 @@ class editor_track
material_handle material1{0};
material_handle material2{0};
material_handle material3{0};
bool frogs{false};
float texlength{4.f};
float texheight{0.6f};
float texwidth{0.9f};

View File

@@ -5213,3 +5213,9 @@ msgstr "Punkt znikania"
msgid "Pedestrian crossing"
msgstr "Przejście dla pieszych"
msgid "Frog and check rails made from the rails"
msgstr "Krzyżownica i kierownice z szyn"
msgid "Cuts the rails of the switch at its frog and lays wing rails and check rails, for rail profiles S60, UIC60 and S39.\nOff for a switch whose include has its own model of them"
msgstr "Tnie szyny rozjazdu na krzyżownicy i układa szyny skrzydłowe i kierownice, dla profili szyn S60, UIC60 i S39.\nWyłącz dla rozjazdu, którego include ma ich własny model"