diff --git a/application/editormode.h b/application/editormode.h index 63607dbe..0d32e7da 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -336,6 +336,7 @@ class editor_mode : public application_mode, private editor_track::observer, pri bool follow{true}; // a track with its formation edited takes it along, the formation led anew bool grown{false}; // the chunks waiting were all asked for by growing: the formations aren't made anew after them bool reshape{false}; // the formations of the tracks laid anew once the chunks waiting are made, as the tracks are put onto the terrain + bool unfitted{false}; // the tracks put onto the terrain are only the ones whose grade line isn't fitted yet double grow_time{-10.0}; double slope{1.5}; double reach{60.0}; @@ -476,7 +477,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri // the whole scenery: tracks, includes and models, moved up or down as one undo step void nmt_lift_scenery(double const Offset); std::size_t nmt_join_ends(); - void nmt_auto_start(); + // Unfitted: only the tracks whose grade line isn't fitted yet, the scenery left at its height + void nmt_auto_start(bool const Unfitted = false); void nmt_auto_step(); // the grade line of the line of tracks through the track fitted to the terrain model and applied; false if it couldn't be. // The line runs over the tracks not Fitted, the ones fitted stay as they are and the line meets them. Where the model falls @@ -2346,6 +2348,19 @@ class editor_mode : public application_mode, private editor_track::observer, pri bool m_profiles_loaded{false}; void profile_library_load(); void profile_library_mark(std::vector Layers); + // the tracks whose grade line was fitted, by name, with the shape they had then, kept as "//$f" comment lines: a track + // changed since, by hand or taken back, isn't fitted any more + struct fitted_mark + { + scene::layer_handle layer{null_handle}; + std::uint64_t shape{0}; + }; + std::map m_fitted; + bool m_fitted_loaded{false}; + void fitted_load(); + void fitted_set(std::vector const &Tracks); + bool grade_fitted(TTrack const &Track); + static std::uint64_t fitted_shape(TTrack const &Track); int profile_find(bool &Reversed) const; bool profile_restore(); bool profile_restore_parts(); diff --git a/application/editormodenmt.cpp b/application/editormodenmt.cpp index 278e24ca..59e394b8 100644 --- a/application/editormodenmt.cpp +++ b/application/editormodenmt.cpp @@ -1170,6 +1170,21 @@ void editor_mode::render_track_terrain(std::vector const &Tracks) auto const busy{nmt_busy()}; auto const spacing{ImGui::GetStyle().ItemSpacing.x}; auto const half{std::floor((ImGui::GetContentRegionAvail().x - spacing) * 0.5f)}; + { + std::size_t rails{0}, done{0}; + for (auto const *track : Tracks) + if (track != nullptr && rail(*track)) + { + ++rails; + if (grade_fitted(*track)) + ++done; + } + if (rails == 1) + ImGui::TextDisabled("%s", done > 0 ? STR_C("Its grade line is fitted to the terrain") : STR_C("Its grade line isn't fitted to the terrain yet")); + else if (rails > 1) + ImGui::TextDisabled(STR_C("Grade lines fitted to the terrain: %zu of %zu"), done, rails); + item_tooltip("A track is fitted when its grade line was fitted to the terrain model or applied with the profile, and it wasn't changed since"); + } ImGui::BeginDisabled(busy); if (ImGui::Button(STR_C("Draw the terrain to the track"), ImVec2(half, 0.0f))) nmt_pull_terrain(Tracks); @@ -1191,11 +1206,24 @@ void editor_mode::render_track_terrain(std::vector const &Tracks) ImGui::TextDisabled("%s", STR_C("All the tracks of the scenery")); toggle_chips("nmtshape", {{STR_C("Formation and slopes along the tracks"), &state.shape, "Each track gets its formation with slopes 1:1.5 down or up to the terrain, as a modifier of the terrain; bridges and tunnels keep the model under them"}}); + std::size_t unfitted{0}, all{0}; + for (auto const *track : simulation::Paths.sequence()) + if (track != nullptr && false == track->m_editorremoved && rail(*track)) + { + ++all; + if (false == grade_fitted(*track)) + ++unfitted; + } ImGui::BeginDisabled(busy || false == m_nmt.placed()); if (ImGui::Button(STR_C("Tracks onto the terrain"), ImVec2(half, 0.0f))) nmt_auto_start(); item_tooltip("The terrain model is loaded along the tracks and the tracks go onto it, wherever the scenery was built: the top of the ballast at the height of the model, the grade line of every line fitted to it within the limits of the line, switches and the tracks beside them on one grade line, the formation and the slopes made along them. Where the model is off the tracks keep their line and it's reported"); ImGui::SameLine(); + ImGui::BeginDisabled(unfitted == 0); + if (ImGui::Button(format(STR_C("Only the ones not fitted (%zu of %zu)"), unfitted, all).c_str(), ImVec2(-FLT_MIN, 0.0f))) + nmt_auto_start(true); + ImGui::EndDisabled(); + item_tooltip("The same for the tracks whose grade line isn't fitted yet, or was changed since: the ones fitted stay as they are and the new lines meet them; the scenery isn't moved"); if (ImGui::Button(STR_C("Terrain under the tracks"), ImVec2(-FLT_MIN, 0.0f))) nmt_under_start(); item_tooltip("The tracks aren't touched: the terrain model is loaded along them and drawn to them, at each place by as much as the tracks lie above or below it there, fading out away from them, so its relief stays and the tracks don't end up in deep cuttings; then the formation and the slopes along them. For a scenery built level or at its own heights"); @@ -1685,15 +1713,16 @@ std::size_t editor_mode::nmt_join_ends() return joined; } -void editor_mode::nmt_auto_start() +void editor_mode::nmt_auto_start(bool const Unfitted) { auto &state{m_nmt_state}; state.error.clear(); state.under = false; + state.unfitted = Unfitted; nmt_locate(); state.probes.clear(); for (auto const &place : nmt_track_places(20.0)) - if (place.shapes) + if (place.shapes && (false == Unfitted || false == grade_fitted(*place.track))) state.probes.push_back(place); if (state.probes.empty()) { @@ -2446,28 +2475,41 @@ void editor_mode::nmt_auto_step() scenery.push_back(track); std::string reason; state.lifted = 0.0; - state.offset = 0.0; - if (std::abs(lift) > 0.05 && tracks_editable(scenery, reason)) + if (state.unfitted) { - nmt_lift_scenery(lift); - state.lifted = lift; + // the tracks fitted already stay where they are, and so does the offset of the model they were fitted to + WriteLog(format("Editor: NMT - the tracks not fitted yet lie %+.2f m against the terrain model, its offset kept at %+.2f m", -lift - state.offset, state.offset), logtype::generic); } - else if (std::abs(lift) > 0.05) + else { - state.offset = -lift; - WriteLog(format("Editor: NMT - the terrain model offset %+.2f m to the scenery, which can't be moved: ", -lift) + reason, logtype::generic); + state.offset = 0.0; + if (std::abs(lift) > 0.05 && tracks_editable(scenery, reason)) + { + nmt_lift_scenery(lift); + state.lifted = lift; + } + else if (std::abs(lift) > 0.05) + { + state.offset = -lift; + WriteLog(format("Editor: NMT - the terrain model offset %+.2f m to the scenery, which can't be moved: ", -lift) + reason, logtype::generic); + } } state.fitted = true; nmt_join_ends(); // the longest tracks first: the main lines lead, the sidings join them state.left.clear(); for (auto *track : simulation::Paths.sequence()) - if (track != nullptr && rail(*track)) + if (track != nullptr && rail(*track) && (false == state.unfitted || false == grade_fitted(*track))) state.left.push_back(track); std::sort(state.left.begin(), state.left.end(), [](TTrack *Left, TTrack *Right) { return Left->Length() > Right->Length(); }); state.tracks = state.left.size(); state.split = 0; state.done.clear(); + // the tracks fitted before are lines fitted already: the new ones run up to them and meet them as they are + if (state.unfitted) + for (auto *track : simulation::Paths.sequence()) + if (track != nullptr && rail(*track) && grade_fitted(*track)) + state.done.push_back(track); state.laid.clear(); state.bridged.clear(); // every path sampled as laid, for the crossings of the lines diff --git a/application/editormodeprofile.cpp b/application/editormodeprofile.cpp index 31835241..f0fa9942 100644 --- a/application/editormodeprofile.cpp +++ b/application/editormodeprofile.cpp @@ -83,6 +83,7 @@ double nice_step(double const Approximate) } std::string const kProfileMark{"//$p"}; +std::string const kFittedMark{"//$f"}; profile::line reversed_line(profile::line Line, double const Length) { @@ -248,6 +249,90 @@ void editor_mode::profile_library_mark(std::vector Layers) } } +std::uint64_t editor_mode::fitted_shape(TTrack const &Track) +{ + // the points of the paths to a centimetre: what's read back from the files is the same shape + std::uint64_t hash{1469598103934665603ull}; + auto const add = [&](std::int64_t const Value) { + for (int i = 0; i < 8; ++i) + hash = (hash ^ static_cast((Value >> (8 * i)) & 0xff)) * 1099511628211ull; + }; + for (auto const &path : Track.m_paths) + for (auto const &point : path.points) + for (int axis = 0; axis < 3; ++axis) + add(std::llround(point[axis] * 100.0)); + return hash; +} + +void editor_mode::fitted_load() +{ + if (m_fitted_loaded) + return; + m_fitted_loaded = true; + m_fitted.clear(); + for (auto const layer : scene::Layers.marked_layers(kFittedMark)) + for (auto const &text : scene::Layers.marked(layer, kFittedMark)) + { + std::istringstream line{text}; + std::string keyword, name, shape; + line >> keyword >> name >> shape; + if (keyword == "fitted" && false == line.fail()) + m_fitted[name] = {layer, std::strtoull(shape.c_str(), nullptr, 16)}; + } +} + +void editor_mode::fitted_set(std::vector const &Tracks) +{ + fitted_load(); + std::vector layers; + for (auto const *track : Tracks) + { + if (track == nullptr || track->m_editorremoved || track->name().empty()) + continue; + scene::layer_handle layer{null_handle}; + if (false == scene::Layers.empty()) + { + layer = scene::Layers.resolve(track->layer()); + if (false == scene::Layers.valid(layer) || false == scene::Layers.accepts(layer)) + layer = scene::Layers.active(); + } + if (auto const found{m_fitted.find(track->name())}; found != m_fitted.end() && found->second.layer != layer) + layers.push_back(found->second.layer); + m_fitted[track->name()] = {layer, fitted_shape(*track)}; + layers.push_back(layer); + } + if (scene::Layers.empty()) + return; + std::sort(layers.begin(), layers.end()); + layers.erase(std::unique(layers.begin(), layers.end()), layers.end()); + for (auto const layer : layers) + { + if (false == scene::Layers.valid(layer)) + continue; + // the marks of the tracks there are no more, or changed since, aren't written again + std::vector lines; + for (auto const &[name, mark] : m_fitted) + { + if (mark.layer != layer) + continue; + auto const *track{simulation::Paths.find(name)}; + if (track == nullptr || track->m_editorremoved || fitted_shape(*track) != mark.shape) + continue; + if (lines.empty()) + lines.emplace_back("# the tracks whose grade line was fitted in the track editor, with their shape then; the simulation skips these lines"); + lines.push_back(format("fitted %s %016llx", name.c_str(), static_cast(mark.shape))); + } + scene::Layers.mark(layer, kFittedMark, std::move(lines)); + } +} + +bool editor_mode::grade_fitted(TTrack const &Track) +{ + fitted_load(); + auto const found{m_fitted.find(Track.name())}; + return found != m_fitted.end() && found->second.shape == fitted_shape(Track); +} + int editor_mode::profile_find(bool &Reversed) const { auto const &state{m_profile}; @@ -1507,6 +1592,11 @@ void editor_mode::profile_apply(bool const Terrain, std::vector... the grade line of a straight over rolling ground of each length fitted, as at a click, and the time the // frame waits for it while the track is laid, the line fitted on the thread of the fitter +// nmtauto [unfitted] the tracks put onto the terrain model, all or the ones whose grade line isn't fitted yet +// fittedstate [reload] logs how many tracks have their grade line fitted, and which; reload: read again from the //$f lines // trackterrain ... the terrain drawn to the tracks, or their formations taken out of it, as the panel of the tracks // terrainhash a hash of the heights of the terrain chunks in memory, to compare the ground two builds made // rebuildall the geometry of every path in the sections made already laid anew, the time it took logged, diff --git a/application/editormodeselftestterrain.cpp b/application/editormodeselftestterrain.cpp index a9d7cec9..e3ff2889 100644 --- a/application/editormodeselftestterrain.cpp +++ b/application/editormodeselftestterrain.cpp @@ -165,9 +165,42 @@ editor_mode::selftest_result editor_mode::selftest_terrain(std::string const &co } if (command == "nmtauto") { - // the tracks put onto the terrain model, as the button of the terrain panel does; nmtwait waits for the end - nmt_auto_start(); - WriteLog("SELFTEST nmtauto: " + m_nmt_state.status); + // the tracks put onto the terrain model, as the button of the panel of the tracks does, all or the ones not fitted yet; + // nmtwait waits for the end + std::string which; + words >> which; + nmt_auto_start(which == "unfitted"); + WriteLog("SELFTEST nmtauto " + which + ": " + m_nmt_state.status); + return selftest_result::handled; + } + if (command == "fittedstate") + { + // the tracks whose grade line is fitted, as the panel of the tracks tells it; reload: the marks read again from the lines + // they go to the files with + std::string reload; + words >> reload; + if (reload == "reload") + { + m_fitted.clear(); + m_fitted_loaded = false; + } + std::size_t rails{0}, done{0}; + std::string names; + for (auto *track : simulation::Paths.sequence()) + if (track != nullptr && false == track->m_editorremoved && (track->iCategoryFlag & 1) != 0) + { + ++rails; + if (grade_fitted(*track)) + { + ++done; + if (names.size() < 200) + names += " " + track->name(); + } + } + std::size_t written{0}; + for (auto const layer : scene::Layers.marked_layers("//$f")) + written += scene::Layers.marked(layer, "//$f").size(); + WriteLog(format("SELFTEST fittedstate: %zu of %zu tracks fitted, %zu marks kept, %zu lines for the files;", done, rails, m_fitted.size(), written) + names); return selftest_result::handled; } if (command == "nmtprobe") diff --git a/lang/editor_pl.po b/lang/editor_pl.po index e2389e7f..056d0a54 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -6785,6 +6785,24 @@ msgstr "Tam, gdzie kończy się teren scenerii, NMT jest dokładany wokół kame msgid "Height offset of the model" msgstr "Przesunięcie wysokości modelu" +msgid "Its grade line is fitted to the terrain" +msgstr "Niweleta dopasowana do terenu" + +msgid "Its grade line isn't fitted to the terrain yet" +msgstr "Niweleta jeszcze niedopasowana do terenu" + +msgid "Grade lines fitted to the terrain: %zu of %zu" +msgstr "Niwelety dopasowane do terenu: %zu z %zu" + +msgid "A track is fitted when its grade line was fitted to the terrain model or applied with the profile, and it wasn't changed since" +msgstr "Tor jest dopasowany, gdy jego niweletę dopasowano do NMT albo zastosowano w profilu i od tego czasu się nie zmienił" + +msgid "Only the ones not fitted (%zu of %zu)" +msgstr "Tylko niedopasowane (%zu z %zu)" + +msgid "The same for the tracks whose grade line isn't fitted yet, or was changed since: the ones fitted stay as they are and the new lines meet them; the scenery isn't moved" +msgstr "To samo dla torów, których niweleta nie jest jeszcze dopasowana albo zmieniła się od tego czasu: dopasowane zostają bez zmian, a nowe niwelety się z nimi łączą; sceneria nie jest przesuwana" + msgid "By hand: the model as it is, the tracks stay" msgstr "Ręcznie: NMT bez zmian, tory zostają"