mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 10:24:53 +02:00
feat(editor): the vertical profile shows the terrain model, the terrain follows it, the camera follows the plot
- The plot draws the ground of the terrain model (NMT) as measured, before any earthworks. The squares under the route are fetched in the background. The tooltip gives the grade line's distance from the model. - Applying a profile lays the formation of the changed tracks anew, or reshapes the route's earthworks when there are any, so the heightmap terrain follows the new grade line. The terrain step is joined to the track step: one undo takes both back. - In 3D and in the top view, the camera shows the part of the route the plot shows once the plot stops moving. It keeps clear of the floating plot window. A toggle turns this off. - nmt_shape_tracks can shape only the given tracks, removing the modifiers of their paths that are gone.
This commit is contained in:
@@ -402,7 +402,9 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
// Rebuild: the chunks there already are made anew too, and the formation laid before is forgotten, as when the tracks moved
|
||||
void nmt_make(std::vector<heightmap::chunk_key> const &Keys, bool const Rebuild = false);
|
||||
bool add_chunk_from_nmt(heightmap::chunk_key const &Key);
|
||||
void nmt_shape_tracks();
|
||||
// Only: the paths of these tracks are shaped anew, the modifiers of those removed taken away; null: all of them. the number
|
||||
// of the modifiers laid
|
||||
std::size_t nmt_shape_tracks(std::vector<TTrack *> const *Only = nullptr);
|
||||
void nmt_update();
|
||||
void render_nmt_ui();
|
||||
// a scenery begun from nothing, with no tracks nor terrain, is asked as the first track is laid whether it wants the terrain
|
||||
@@ -2275,6 +2277,9 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
std::vector<editor_track::route_sample> samples;
|
||||
std::vector<TTrack *> created; // the paths the last apply split off the route
|
||||
std::vector<double> terrain; // NaN where there's no terrain
|
||||
std::vector<double> model; // the ground of the terrain model (NMT) as measured; NaN where it's not known
|
||||
bool model_waiting{false}; // squares of the model under the route still to come
|
||||
double model_polled{0.0};
|
||||
// what the joined ends take their height and grade from, when it's a switch: shown on the plot
|
||||
std::string start_inherits, end_inherits;
|
||||
// depth of the formation under the grade line, the rail top, of the track of each sample: the formation, not the rails,
|
||||
@@ -2306,7 +2311,13 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
double view_centre{0.0};
|
||||
float exaggeration{20.0f};
|
||||
bool show_terrain{true};
|
||||
bool show_model{true};
|
||||
bool show_track{true};
|
||||
bool camera_follows{true};
|
||||
// the part of the route the plot shows: as last seen and when, and as the camera last showed it
|
||||
std::pair<double, double> view_seen{0.0, 0.0};
|
||||
double view_seen_time{0.0};
|
||||
std::pair<double, double> view_framed{0.0, 0.0};
|
||||
bool show_plan{true};
|
||||
int selected{-1};
|
||||
int dragging{-1};
|
||||
@@ -2368,6 +2379,8 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
void profile_open_selection(std::vector<TTrack *> const &Tracks);
|
||||
void profile_follow_selection();
|
||||
void profile_resample();
|
||||
// the ground of the terrain model under the route, as it was measured before any earthworks
|
||||
void profile_sample_model();
|
||||
void profile_recognize();
|
||||
void profile_fit_ground();
|
||||
// depth of the foot of the ballast under the rail top, and its distance from the axis
|
||||
@@ -2379,8 +2392,14 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
void profile_restore_ground();
|
||||
void render_profile_earthworks();
|
||||
void profile_check();
|
||||
void profile_apply();
|
||||
// Terrain: the heightmap terrain follows the tracks changed
|
||||
void profile_apply(bool const Terrain = false);
|
||||
// the terrain under the tracks changed by the last Edits steps of the history which changed tracks
|
||||
void profile_follow_terrain(std::size_t const Edits);
|
||||
void profile_fit_view();
|
||||
// the camera shows the part of the route the plot shows, once the plot stops moving
|
||||
void profile_follow_view();
|
||||
void profile_frame_view();
|
||||
void profile_after_undo();
|
||||
void profile_follow_edits();
|
||||
struct profile_view;
|
||||
|
||||
@@ -508,11 +508,12 @@ bool editor_mode::add_chunk_from_nmt(heightmap::chunk_key const &Key)
|
||||
return true;
|
||||
}
|
||||
|
||||
void editor_mode::nmt_shape_tracks()
|
||||
std::size_t editor_mode::nmt_shape_tracks(std::vector<TTrack *> const *Only)
|
||||
{
|
||||
auto &state{m_nmt_state};
|
||||
if (false == ensure_terrain())
|
||||
return;
|
||||
return 0;
|
||||
auto const wanted = [&](TTrack const *Track) { return Only == nullptr || std::find(Only->begin(), Only->end(), Track) != Only->end(); };
|
||||
// one modifier per path: the formation under its axis and the slopes from its edges to the ground. laid over each other the
|
||||
// modifiers keep the highest of the slopes going down and the lowest of those going up, so tracks side by side share one
|
||||
// formation instead of a saw of their own ones
|
||||
@@ -524,6 +525,8 @@ void editor_mode::nmt_shape_tracks()
|
||||
bool broken{false};
|
||||
for (auto const &place : nmt_track_places(step))
|
||||
{
|
||||
if (false == wanted(place.track))
|
||||
continue;
|
||||
std::pair<TTrack *, int> const path{place.track, place.path};
|
||||
if (path != previous)
|
||||
broken = false;
|
||||
@@ -552,9 +555,28 @@ void editor_mode::nmt_shape_tracks()
|
||||
for (auto &[path, modifier] : modifiers)
|
||||
if (modifier.points.size() >= 2)
|
||||
laid.push_back(std::move(modifier));
|
||||
if (Only != nullptr)
|
||||
{
|
||||
// the modifiers the tracks don't have any more: of the pieces of a path gone, of the tracks removed
|
||||
std::vector<std::string> gone;
|
||||
for (auto const *track : *Only)
|
||||
{
|
||||
if (track == nullptr || track->name().empty())
|
||||
continue;
|
||||
auto const own{"nmt " + track->name()};
|
||||
for (auto const &modifier : m_streamer.modifiers())
|
||||
if ((modifier.name == own || modifier.name.starts_with(own + " ")) && std::none_of(laid.begin(), laid.end(), [&](heightmap::modifier const &Laid) { return Laid.name == modifier.name; }))
|
||||
gone.push_back(modifier.name);
|
||||
}
|
||||
for (auto const &name : gone)
|
||||
m_streamer.remove_modifier(name);
|
||||
}
|
||||
auto const count{laid.size()};
|
||||
m_streamer.modifiers(std::move(laid));
|
||||
forget_ground();
|
||||
state.shaped = modifiers.size();
|
||||
if (Only == nullptr)
|
||||
state.shaped = modifiers.size();
|
||||
return count;
|
||||
}
|
||||
|
||||
void editor_mode::nmt_update()
|
||||
|
||||
@@ -22,6 +22,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "scene/scenelayers.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/glmHelpers.h"
|
||||
#include "world/TrackSleepers.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/translation.h"
|
||||
@@ -46,6 +47,7 @@ ImU32 const kTangents{IM_COL32(255, 210, 60, 130)};
|
||||
ImU32 const kTrack{IM_COL32(190, 190, 190, 255)};
|
||||
ImU32 const kTerrain{IM_COL32(170, 120, 70, 255)};
|
||||
ImU32 const kFormation{IM_COL32(215, 185, 140, 200)};
|
||||
ImU32 const kModel{IM_COL32(70, 200, 190, 230)}; // the ground of the terrain model, as measured
|
||||
double constexpr kNoBed{0.05}; // m, a trackbed shallower is none
|
||||
// a design the track lay on when it was kept, within kApplied, was applied; where the track parts from it by more than kStale since,
|
||||
// it was changed by other tools, and the line is read from the track there, over the stretch and kStaleMargin around it
|
||||
@@ -730,6 +732,8 @@ void editor_mode::profile_take(editor_track::route Route, TTrack *From, TTrack *
|
||||
}
|
||||
}
|
||||
profile_fit_view();
|
||||
// the camera stays where the route was picked, it follows the plot once that's moved
|
||||
state.view_seen = state.view_framed = {state.view_from, state.view_to};
|
||||
}
|
||||
|
||||
void editor_mode::profile_resample()
|
||||
@@ -747,6 +751,7 @@ void editor_mode::profile_resample()
|
||||
for (std::size_t i = 0; i < samples.size(); ++i)
|
||||
if (found[i] != 0)
|
||||
state.terrain[i] = heights[i];
|
||||
profile_sample_model();
|
||||
state.beds.assign(samples.size(), 0.0);
|
||||
std::vector<char> bedless(samples.size(), 0);
|
||||
std::vector<double> own;
|
||||
@@ -837,6 +842,52 @@ void editor_mode::profile_resample()
|
||||
profile_check();
|
||||
}
|
||||
|
||||
void editor_mode::profile_sample_model()
|
||||
{
|
||||
auto &state{m_profile};
|
||||
auto const &samples{state.samples};
|
||||
state.model.assign(samples.size(), std::numeric_limits<double>::quiet_NaN());
|
||||
state.model_waiting = false;
|
||||
state.model_polled = ImGui::GetTime();
|
||||
if (samples.empty() || false == state.show_model)
|
||||
return;
|
||||
nmt_locate();
|
||||
if (false == m_nmt.placed())
|
||||
return;
|
||||
// the squares along the route, not all those under the rectangle around it: a long line crosses the grid
|
||||
std::size_t constexpr run{200};
|
||||
for (std::size_t first = 0; first < samples.size(); first += run)
|
||||
{
|
||||
glm::dvec2 low{samples[first].position.x, samples[first].position.z};
|
||||
auto high{low};
|
||||
for (auto i = first; i < std::min(samples.size(), first + run + 1); ++i)
|
||||
{
|
||||
low = glm::min(low, glm::dvec2{samples[i].position.x, samples[i].position.z});
|
||||
high = glm::max(high, glm::dvec2{samples[i].position.x, samples[i].position.z});
|
||||
}
|
||||
if (false == m_nmt.request(low - glm::dvec2{2.0}, high + glm::dvec2{2.0}))
|
||||
state.model_waiting = true;
|
||||
}
|
||||
std::vector<double> differences;
|
||||
for (std::size_t i = 0; i < samples.size(); ++i)
|
||||
if (double height; m_nmt.height(samples[i].position.x, samples[i].position.z, height))
|
||||
{
|
||||
state.model[i] = height;
|
||||
differences.push_back(samples[i].position.y - height);
|
||||
}
|
||||
// a scenery put onto the model lies at its heights, or as far from them as the model was found to lie from it; one built at
|
||||
// heights of its own, never put onto it, is compared by the shape: the model goes as far as the route lies from it
|
||||
auto offset{m_nmt_state.offset};
|
||||
if (false == m_nmt_state.fitted && false == differences.empty())
|
||||
{
|
||||
std::nth_element(differences.begin(), differences.begin() + differences.size() / 2, differences.end());
|
||||
if (auto const median{differences[differences.size() / 2]}; std::abs(median) > 30.0)
|
||||
offset = median;
|
||||
}
|
||||
for (auto &height : state.model)
|
||||
height += offset;
|
||||
}
|
||||
|
||||
void editor_mode::profile_recognize()
|
||||
{
|
||||
auto &state{m_profile};
|
||||
@@ -1306,13 +1357,101 @@ void editor_mode::profile_fit_view()
|
||||
state.exaggeration = 0.0f;
|
||||
}
|
||||
|
||||
void editor_mode::profile_apply()
|
||||
void editor_mode::profile_follow_view()
|
||||
{
|
||||
auto &state{m_profile};
|
||||
std::pair<double, double> const shown{state.view_from, state.view_to};
|
||||
auto const now{ImGui::GetTime()};
|
||||
if (shown != state.view_seen)
|
||||
{
|
||||
state.view_seen = shown;
|
||||
state.view_seen_time = now;
|
||||
return;
|
||||
}
|
||||
if (false == state.camera_follows || shown == state.view_framed || now - state.view_seen_time < 0.25)
|
||||
return;
|
||||
state.view_framed = shown;
|
||||
profile_frame_view();
|
||||
}
|
||||
|
||||
void editor_mode::profile_frame_view()
|
||||
{
|
||||
auto const &state{m_profile};
|
||||
std::vector<glm::dvec3> points;
|
||||
for (auto const &sample : state.samples)
|
||||
if (sample.chainage >= state.view_from && sample.chainage <= state.view_to)
|
||||
points.push_back(sample.position);
|
||||
auto const &io{ImGui::GetIO()};
|
||||
if (points.size() < 2 || io.DisplaySize.y <= 0.0f)
|
||||
return;
|
||||
glm::dvec3 low{points.front()};
|
||||
glm::dvec3 high{points.front()};
|
||||
for (auto const &point : points)
|
||||
{
|
||||
low = glm::min(low, point);
|
||||
high = glm::max(high, point);
|
||||
}
|
||||
auto const centre{(low + high) * 0.5};
|
||||
// the room the docks and the strip of the plot leave to the view, and how far its middle lies from the middle of the screen,
|
||||
// in pixels
|
||||
glm::dvec2 const screen{io.DisplaySize.x, io.DisplaySize.y};
|
||||
auto const viewlow{editor_ui::view_min()};
|
||||
auto viewhigh{editor_ui::view_max()};
|
||||
if (state.strip_top > viewlow.y + 100.0f)
|
||||
viewhigh.y = std::min(viewhigh.y, state.strip_top);
|
||||
auto const width{std::max(50.0, static_cast<double>(viewhigh.x - viewlow.x))};
|
||||
auto const height{std::max(50.0, static_cast<double>(viewhigh.y - viewlow.y))};
|
||||
glm::dvec2 const shift{(viewlow.x + viewhigh.x) * 0.5 - screen.x * 0.5, (viewlow.y + viewhigh.y) * 0.5 - screen.y * 0.5};
|
||||
double constexpr margin{1.1};
|
||||
if (Global.EditorOrtho)
|
||||
{
|
||||
auto const yaw{static_cast<double>(Camera.Angle.y)};
|
||||
double extent{5.0};
|
||||
for (auto const &point : points)
|
||||
{
|
||||
auto const local{RotateY(point - centre, -yaw)};
|
||||
extent = std::max({extent, std::abs(local.x) * margin * screen.y / width, std::abs(local.z) * margin * screen.y / height});
|
||||
}
|
||||
Global.EditorOrthoExtent = std::clamp(static_cast<float>(extent), 5.0f, 50000.0f);
|
||||
auto const scale{2.0 * static_cast<double>(Global.EditorOrthoExtent) / screen.y};
|
||||
auto const offset{RotateY(glm::dvec3{shift.x * scale, 0.0, shift.y * scale}, yaw)};
|
||||
Camera.Pos.x = centre.x - offset.x;
|
||||
Camera.Pos.z = centre.z - offset.z;
|
||||
Camera.Velocity = {};
|
||||
m_focus_active = false;
|
||||
Global.pCamera = Camera;
|
||||
return;
|
||||
}
|
||||
// seen from the side, as the plot shows it: the start on the left, from a little above
|
||||
glm::dvec3 chord{points.back().x - points.front().x, 0.0, points.back().z - points.front().z};
|
||||
chord = glm::length(chord) > 1e-6 ? glm::normalize(chord) : glm::dvec3{1.0, 0.0, 0.0};
|
||||
glm::dvec3 const up{0.0, 1.0, 0.0};
|
||||
auto const look{glm::normalize(glm::cross(up, chord) + glm::dvec3{0.0, -0.4, 0.0})};
|
||||
auto const right{glm::normalize(glm::cross(look, up))};
|
||||
auto const above{glm::cross(right, look)};
|
||||
auto const tangent{std::tan(glm::radians(static_cast<double>(Global.FieldOfView) / std::max(0.01, static_cast<double>(Global.ZoomFactor))) * 0.5)};
|
||||
auto const across{tangent * width / screen.y};
|
||||
auto const upward{tangent * height / screen.y};
|
||||
double distance{10.0};
|
||||
for (auto const &point : points)
|
||||
{
|
||||
auto const offset{point - centre};
|
||||
auto const depth{glm::dot(offset, look)};
|
||||
distance = std::max({distance, std::abs(glm::dot(offset, right)) * margin / across - depth, std::abs(glm::dot(offset, above)) * margin / upward - depth});
|
||||
}
|
||||
distance = std::min(distance, 8000.0);
|
||||
auto const pixel{2.0 * distance * tangent / screen.y};
|
||||
fly_to(centre - right * (shift.x * pixel) + above * (shift.y * pixel), look, distance, 0.35);
|
||||
}
|
||||
|
||||
void editor_mode::profile_apply(bool const Terrain)
|
||||
{
|
||||
auto &state{m_profile};
|
||||
state.status.clear();
|
||||
state.error.clear();
|
||||
if (state.route.spans.empty() || state.line.points.size() < 2)
|
||||
return;
|
||||
auto const edits{m_track_edits};
|
||||
if (has_errors(state.issues))
|
||||
{
|
||||
state.error = STR_C("Correct the errors to apply the grade line");
|
||||
@@ -1370,9 +1509,65 @@ void editor_mode::profile_apply()
|
||||
auto const followed{follow_crossovers(before)};
|
||||
if (followed > 0)
|
||||
state.status += format(STR_C("\nThe tracks across %zu crossover(s) followed the change"), followed);
|
||||
if (Terrain)
|
||||
profile_follow_terrain(static_cast<std::size_t>(m_track_edits - edits));
|
||||
WriteLog("Editor: vertical profile - " + state.status, logtype::generic);
|
||||
}
|
||||
|
||||
void editor_mode::profile_follow_terrain(std::size_t const Edits)
|
||||
{
|
||||
auto &state{m_profile};
|
||||
// the heightmap terrain the scenery has; the ground of a scenery without it stays as it is
|
||||
if (false == m_streamer.active() || Edits == 0)
|
||||
return;
|
||||
std::vector<TTrack *> tracks;
|
||||
std::size_t found{0};
|
||||
for (auto step{m_history.rbegin()}; step != m_history.rend() && found < Edits; ++step)
|
||||
{
|
||||
if (step->action != EditorSnapshot::Action::TrackEdit)
|
||||
continue;
|
||||
++found;
|
||||
for (auto const &entry : step->tracks)
|
||||
tracks.push_back(entry.first);
|
||||
tracks.insert(tracks.end(), step->created.begin(), step->created.end());
|
||||
tracks.insert(tracks.end(), step->removed.begin(), step->removed.end());
|
||||
}
|
||||
std::erase(tracks, nullptr);
|
||||
std::sort(tracks.begin(), tracks.end());
|
||||
tracks.erase(std::unique(tracks.begin(), tracks.end()), tracks.end());
|
||||
if (tracks.empty())
|
||||
return;
|
||||
// the route shaped by the earthworks of the profile keeps its modifier, shaped again to the new grade line; the tracks
|
||||
// shaped one by one, as the terrain model lays them, get their formations anew
|
||||
auto const name = [](TTrack const *Track) { return Track != nullptr && false == Track->name().empty() ? Track->name() : std::string{"?"}; };
|
||||
auto const route{"route " + name(state.from) + " - " + name(state.to)};
|
||||
auto const &modifiers{m_streamer.modifiers()};
|
||||
auto const has = [&](std::string const &Name) { return std::any_of(modifiers.begin(), modifiers.end(), [&](heightmap::modifier const &Modifier) { return Modifier.name == Name; }); };
|
||||
auto const shaped{has(route) || (false == state.earthworks.modifier.empty() && has(state.earthworks.modifier))};
|
||||
auto const own{std::any_of(tracks.begin(), tracks.end(), [&](TTrack const *Track) {
|
||||
return false == Track->name().empty() && std::any_of(modifiers.begin(), modifiers.end(), [&](heightmap::modifier const &Modifier) { return Modifier.name == "nmt " + Track->name() || Modifier.name.starts_with("nmt " + Track->name() + " "); });
|
||||
})};
|
||||
auto status{state.status};
|
||||
std::size_t laid{0};
|
||||
if (false == shaped || own)
|
||||
laid = nmt_shape_tracks(&tracks);
|
||||
if (shaped)
|
||||
{
|
||||
profile_shape_ground();
|
||||
status += "\n" + state.status;
|
||||
}
|
||||
else
|
||||
profile_resample();
|
||||
if (laid > 0)
|
||||
status += format(STR_P("\nThe terrain followed the tracks: %zu formation laid anew", "\nThe terrain followed the tracks: %zu formations laid anew", laid), laid);
|
||||
state.status = status;
|
||||
// the terrain goes back with the grade line in one step
|
||||
auto const before{m_history.empty() ? EditorSnapshot::Action::Other : m_history.back().action};
|
||||
terrain_history_step();
|
||||
if (before != EditorSnapshot::Action::Terrain && false == m_history.empty() && m_history.back().action == EditorSnapshot::Action::Terrain)
|
||||
m_history.back().joined = true;
|
||||
}
|
||||
|
||||
void editor_mode::profile_after_undo()
|
||||
{
|
||||
auto &state{m_profile};
|
||||
@@ -1576,7 +1771,7 @@ void editor_mode::render_profile_header()
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0.2f, 0.55f, 0.25f, 1.0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0.25f, 0.68f, 0.3f, 1.0f));
|
||||
if (ImGui::Button((label + "###profileapply").c_str()))
|
||||
profile_apply();
|
||||
profile_apply(true);
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::EndDisabled();
|
||||
if (ImGui::IsItemHovered(ImGuiHoveredFlags_AllowWhenDisabled))
|
||||
@@ -1974,15 +2169,31 @@ struct editor_mode::profile_view
|
||||
void editor_mode::render_profile_toolbar()
|
||||
{
|
||||
auto &state{m_profile};
|
||||
// the next item on the same line while it fits, past the edge of the plot on the next one
|
||||
auto const edge{ImGui::GetWindowPos().x + ImGui::GetWindowContentRegionMax().x};
|
||||
auto const next = [&](float const Gap, float const Width) {
|
||||
ImGui::SameLine(0.0f, Gap);
|
||||
if (ImGui::GetCursorScreenPos().x + Width > edge)
|
||||
ImGui::NewLine();
|
||||
};
|
||||
auto const pill = [](char const *Label) { return ImGui::CalcTextSize(Label).x + ImGui::GetFrameHeight() * 1.75f; };
|
||||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::TextDisabled("%s", STR_C("Show:"));
|
||||
ImGui::SameLine();
|
||||
next(ImGui::GetStyle().ItemSpacing.x, pill(STR_C("Track")));
|
||||
pill_toggle(STR_C("Track"), kTrack, state.show_track);
|
||||
ImGui::SameLine(0.0f, 4.0f);
|
||||
next(4.0f, pill(STR_C("Terrain")));
|
||||
pill_toggle(STR_C("Terrain"), kTerrain, state.show_terrain);
|
||||
ImGui::SameLine(0.0f, 4.0f);
|
||||
if (m_nmt.placed())
|
||||
{
|
||||
next(4.0f, pill(STR_C("Terrain model")));
|
||||
if (pill_toggle(STR_C("Terrain model"), kModel, state.show_model))
|
||||
profile_sample_model();
|
||||
item_tooltip(state.model_waiting ? "The ground of the terrain model (NMT) as it was measured, before the earthworks; its squares along the route are still being fetched"
|
||||
: "The ground of the terrain model (NMT) as it was measured, before the earthworks");
|
||||
}
|
||||
next(4.0f, pill(STR_C("Plan curvature")));
|
||||
pill_toggle(STR_C("Plan curvature"), kPlan, state.show_plan);
|
||||
ImGui::SameLine(0.0f, 6.0f);
|
||||
next(6.0f, ImGui::CalcTextSize("(?)").x);
|
||||
ImGui::TextDisabled("(?)");
|
||||
if (ImGui::IsItemHovered())
|
||||
{
|
||||
@@ -2003,6 +2214,7 @@ void editor_mode::render_profile_toolbar()
|
||||
entry(kTrack, "Track");
|
||||
entry(kTerrain, "Terrain");
|
||||
entry(kFormation, "Formation");
|
||||
entry(kModel, "Terrain model");
|
||||
entry(kPlan, "Plan curvature");
|
||||
entry(kSwitchLabel, "Switches");
|
||||
entry(kSharedEdge, "Shared timbers");
|
||||
@@ -2010,16 +2222,20 @@ void editor_mode::render_profile_toolbar()
|
||||
entry(kWarning, "Warnings");
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
ImGui::SameLine(0.0f, 14.0f);
|
||||
next(14.0f, 120.0f);
|
||||
ImGui::SetNextItemWidth(120.0f);
|
||||
ImGui::SliderFloat("##exaggeration", &state.exaggeration, 1.0f, 500.0f, STR_C("heights x%.0f"), ImGuiSliderFlags_Logarithmic);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("Exaggeration of the heights against the lengths, also Ctrl+wheel over the plot"));
|
||||
ImGui::SameLine();
|
||||
next(ImGui::GetStyle().ItemSpacing.x, ImGui::CalcTextSize(STR_C("Fit")).x + ImGui::GetStyle().FramePadding.x * 2.0f);
|
||||
if (ImGui::Button(STR_C("Fit")))
|
||||
profile_fit_view();
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("Shows the whole route"));
|
||||
next(14.0f, pill(STR_C("Camera follows")));
|
||||
if (pill_toggle(STR_C("Camera follows"), kGradeLine, state.camera_follows) && state.camera_follows)
|
||||
state.view_framed = {};
|
||||
item_tooltip("The view shows the part of the route the plot shows, once the plot is zoomed or moved");
|
||||
}
|
||||
|
||||
void editor_mode::render_profile_canvas(float const Height)
|
||||
@@ -2035,7 +2251,10 @@ void editor_mode::render_profile_canvas(float const Height)
|
||||
|
||||
if (state.exaggeration <= 0.0f)
|
||||
profile_fit_exaggeration(profile_view{state, corner, size});
|
||||
if (state.model_waiting && ImGui::GetTime() - state.model_polled > 0.5)
|
||||
profile_sample_model();
|
||||
profile_navigate(profile_view{state, corner, size}, hovered);
|
||||
profile_follow_view();
|
||||
profile_view const view{state, corner, size};
|
||||
if (profile_canvas_edit(view, hovered))
|
||||
profile_edited();
|
||||
@@ -2415,6 +2634,8 @@ void editor_mode::draw_profile_canvas(profile_view const &View, ImDrawList &Draw
|
||||
// the formation under the grade line, which is what meets the ground
|
||||
polyline([&](std::size_t const I) { return profile::elevation(state.line, samples[I].chainage) - (I < state.beds.size() ? state.beds[I] : 0.0); }, kFormation, 1.0f);
|
||||
}
|
||||
if (state.show_model && state.model.size() == samples.size())
|
||||
polyline([&](std::size_t const I) { return state.model[I]; }, kModel, 1.5f);
|
||||
if (state.show_track)
|
||||
polyline([&](std::size_t const I) { return samples[I].position.y; }, kTrack, 1.5f);
|
||||
|
||||
@@ -2530,6 +2751,8 @@ void editor_mode::profile_canvas_tooltip() const
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(kFormation), STR_C("Formation %.3f m, %.2f m under the rail top"), formation, designed - formation);
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(kTerrain), STR_C("Terrain %.3f m, %s %.2f m"), state.terrain[index], formation > state.terrain[index] ? STR_C("fill") : STR_C("cut"), std::abs(formation - state.terrain[index]));
|
||||
}
|
||||
if (state.show_model && index < state.model.size() && false == std::isnan(state.model[index]))
|
||||
ImGui::TextColored(ImGui::ColorConvertU32ToFloat4(kModel), STR_C("Terrain model %.3f m, the grade line %+.2f m from it"), state.model[index], designed - state.model[index]);
|
||||
auto const &span{state.route.spans[samples[index].span]};
|
||||
ImGui::TextDisabled("%s%s", span.track->name().c_str(), span.track->eType != tt_Normal ? STR_C(" (switch, tilted as a whole with its branch)") : "");
|
||||
ImGui::EndTooltip();
|
||||
|
||||
@@ -330,6 +330,24 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "vprofilemodel")
|
||||
{
|
||||
// the ground of the terrain model along the open route, and where the camera is
|
||||
auto const &state{m_profile};
|
||||
std::size_t known{0};
|
||||
double low{std::numeric_limits<double>::max()};
|
||||
double high{std::numeric_limits<double>::lowest()};
|
||||
for (std::size_t i = 0; i < state.model.size() && i < state.samples.size(); ++i)
|
||||
if (false == std::isnan(state.model[i]))
|
||||
{
|
||||
++known;
|
||||
low = std::min(low, state.samples[i].position.y - state.model[i]);
|
||||
high = std::max(high, state.samples[i].position.y - state.model[i]);
|
||||
}
|
||||
WriteLog(format("SELFTEST vprofilemodel: %zu of %zu samples known%s, the track %.2f..%.2f m from the model; view %.1f..%.1f, camera %.1f %.1f %.1f angle %.3f %.3f, ortho %d extent %.0f", known, state.samples.size(), state.model_waiting ? " (waiting)" : "", known > 0 ? low : 0.0, known > 0 ? high : 0.0,
|
||||
state.view_from, state.view_to, Camera.Pos.x, Camera.Pos.y, Camera.Pos.z, Camera.Angle.x, Camera.Angle.y, Global.EditorOrtho ? 1 : 0, Global.EditorOrthoExtent));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "vprofilezoom")
|
||||
{
|
||||
// the plot of the open profile from one chainage to another, the heights exaggerated as given (0: fitted)
|
||||
@@ -382,7 +400,8 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm
|
||||
{
|
||||
// a plain grade line from the height of the start of the open route, applied to it
|
||||
double grade{0.0};
|
||||
words >> grade;
|
||||
std::string option;
|
||||
words >> grade >> option;
|
||||
auto &state{m_profile};
|
||||
if (state.samples.empty())
|
||||
{
|
||||
@@ -394,7 +413,7 @@ editor_mode::selftest_result editor_mode::selftest_track(std::string const &comm
|
||||
state.line.join_start = false;
|
||||
state.line.join_end = false;
|
||||
profile_check();
|
||||
profile_apply();
|
||||
profile_apply(option == "terrain");
|
||||
WriteLog("SELFTEST vprofileapply: " + state.status + " " + state.error);
|
||||
for (auto const &span : state.route.spans)
|
||||
{
|
||||
|
||||
@@ -9865,3 +9865,27 @@ msgstr "Nie dało się przeciąć \"%s\", żeby zmienić pasy"
|
||||
|
||||
msgid "Looking for the templates..."
|
||||
msgstr "Szukam szablonów..."
|
||||
|
||||
msgid "Terrain model"
|
||||
msgstr "Teren z NMT"
|
||||
|
||||
msgid "The ground of the terrain model (NMT) as it was measured, before the earthworks; its squares along the route are still being fetched"
|
||||
msgstr "Teren z modelu NMT taki, jak go zmierzono, sprzed robót ziemnych; jego kwadraty wzdłuż trasy jeszcze się pobierają"
|
||||
|
||||
msgid "The ground of the terrain model (NMT) as it was measured, before the earthworks"
|
||||
msgstr "Teren z modelu NMT taki, jak go zmierzono, sprzed robót ziemnych"
|
||||
|
||||
msgid "Terrain model %.3f m, the grade line %+.2f m from it"
|
||||
msgstr "Teren z NMT %.3f m, niweleta %+.2f m od niego"
|
||||
|
||||
msgid "Camera follows"
|
||||
msgstr "Kamera za wykresem"
|
||||
|
||||
msgid "The view shows the part of the route the plot shows, once the plot is zoomed or moved"
|
||||
msgstr "Widok pokazuje ten kawałek trasy, który widać na wykresie, kiedy go przybliżysz albo przesuniesz"
|
||||
|
||||
msgid "\nThe terrain followed the tracks: %zu formation laid anew"
|
||||
msgid_plural "\nThe terrain followed the tracks: %zu formations laid anew"
|
||||
msgstr[0] "\nTeren poszedł za torami: %zu torowisko ułożone od nowa"
|
||||
msgstr[1] "\nTeren poszedł za torami: %zu torowiska ułożone od nowa"
|
||||
msgstr[2] "\nTeren poszedł za torami: %zu torowisk ułożonych od nowa"
|
||||
|
||||
Reference in New Issue
Block a user