mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-02 06:59:13 +01:00
feat(editor): the work of the editor sessions not committed yet, as it stands: a curve traced on from the end of the diverging track of a switch goes on at its radius with no transition in, or from a straight right at the switch (alignment start_held, fit_going_on), the plan taking an end adjoining R 10 km or more for a straight; the switches and slips laid into curves, slid along them and laid alone on one straight (frame_path, switch slide); the route plan reading curves going on from the tracks adjoining, the kept designs taken or forgotten; the orthophoto and terrain model settings, downloads and layers, the tracks onto the terrain; the curvature of a path in the plan (bezier::plan_curvature); the Polish texts of all of it. Not built as committed
This commit is contained in:
@@ -80,6 +80,38 @@ namespace
|
||||
using vec3 = glm::vec3;
|
||||
using dvec2 = glm::dvec2;
|
||||
|
||||
// the steps of a frame of the editor timed: a frame whose update takes longer than kSlow tells in the log which of them took
|
||||
// the time, at most twice a second
|
||||
class frame_steps
|
||||
{
|
||||
public:
|
||||
void step(char const *Name)
|
||||
{
|
||||
auto const now{std::chrono::steady_clock::now()};
|
||||
m_steps.emplace_back(Name, std::chrono::duration<double, std::milli>(now - m_last).count());
|
||||
m_last = now;
|
||||
}
|
||||
~frame_steps()
|
||||
{
|
||||
constexpr double kSlow{50.0}; // ms
|
||||
auto const total{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - m_start).count()};
|
||||
static auto told{std::chrono::steady_clock::time_point{}};
|
||||
if (total < kSlow || m_start - told < std::chrono::milliseconds{500})
|
||||
return;
|
||||
told = m_start;
|
||||
std::string text{"Editor: slow frame, update " + std::to_string(static_cast<int>(total)) + " ms:"};
|
||||
for (auto const &[name, time] : m_steps)
|
||||
if (time >= 3.0)
|
||||
text += " " + std::string{name} + " " + std::to_string(static_cast<int>(time));
|
||||
WriteLog(text, logtype::generic);
|
||||
}
|
||||
|
||||
private:
|
||||
std::chrono::steady_clock::time_point const m_start{std::chrono::steady_clock::now()};
|
||||
std::chrono::steady_clock::time_point m_last{m_start};
|
||||
std::vector<std::pair<char const *, double>> m_steps;
|
||||
};
|
||||
|
||||
inline bool is_release(int state)
|
||||
{
|
||||
return state == GLFW_RELEASE;
|
||||
@@ -552,14 +584,21 @@ editor_mode::editor_mode() {
|
||||
});
|
||||
m_roadtool.settings.normalize();
|
||||
road_select(nullptr);
|
||||
// the orthophoto is fitted onto the same ground as the area fill uses, terrain tile models included
|
||||
ground_sources(m_orthophoto);
|
||||
ui()->set_base_layers([this]() { render_base_layers(); });
|
||||
editor_track::observe(this);
|
||||
}
|
||||
|
||||
void editor_mode::ground_sources(editor_orthophoto &Layer)
|
||||
{
|
||||
// the imagery is fitted onto the same ground as the area fill uses, terrain tile models included
|
||||
// the roads built in the editor aren't ground: the imagery goes under them
|
||||
m_orthophoto.ground_source([](glm::dvec2 const &Min, glm::dvec2 const &Max, bool const Heightmap, std::vector<world_triangle> &Out) {
|
||||
Layer.ground_source([](glm::dvec2 const &Min, glm::dvec2 const &Max, bool const Heightmap, std::vector<world_triangle> &Out) {
|
||||
gather_ground_triangles(Min, Max, true, 50.0f, Out, true, Heightmap);
|
||||
});
|
||||
m_orthophoto.height_source([](double const X, double const Z, double &Height) { return EditorTerrain.height_at(X, Z, Height); });
|
||||
Layer.height_source([](double const X, double const Z, double &Height) { return EditorTerrain.height_at(X, Z, Height); });
|
||||
// the imagery often comes before the terrain made from the model: it lies where the terrain will be
|
||||
m_orthophoto.guess_source([this](double const X, double const Z, double &Height) {
|
||||
Layer.guess_source([this](double const X, double const Z, double &Height) {
|
||||
if (nmt_ground(X, Z, Height))
|
||||
return true;
|
||||
// asked for, the tile goes onto it once it comes
|
||||
@@ -567,8 +606,7 @@ editor_mode::editor_mode() {
|
||||
m_nmt.request({X - 1.0, Z - 1.0}, {X + 1.0, Z + 1.0});
|
||||
return false;
|
||||
});
|
||||
editor_track::observe(this);
|
||||
}
|
||||
}
|
||||
|
||||
editor_mode::~editor_mode()
|
||||
{
|
||||
@@ -657,6 +695,13 @@ bool editor_mode::init()
|
||||
{
|
||||
EditorSettings.load();
|
||||
Global.EditorOrthoTracks = EditorSettings.top_tracks();
|
||||
// the terrain model as the user left it; the settings are read only here, after the mode was made
|
||||
auto const &model{EditorSettings.terrain_model()};
|
||||
m_nmt_state.grow = EditorSettings.terrain_while_laying();
|
||||
m_nmt_state.band = std::clamp(model.band, 50.0, 3000.0);
|
||||
m_nmt_state.shape = model.shape;
|
||||
m_nmt_state.follow = model.follow;
|
||||
m_terrain_add_count = std::clamp(model.around, 1, 8);
|
||||
EditorModelSets.load();
|
||||
EditorTrackPresets.load();
|
||||
Camera.Init({0, 15, 0}, {glm::radians(-30.0), glm::radians(180.0), 0}, nullptr);
|
||||
@@ -1344,8 +1389,10 @@ void editor_mode::redo_one()
|
||||
|
||||
bool editor_mode::update()
|
||||
{
|
||||
frame_steps steps;
|
||||
Global.editor_tracks = (current_work_area() == work_area::tracks);
|
||||
selftest_step();
|
||||
steps.step("selftest");
|
||||
// the drives of the switches go to the tips of their blades once, as the templates of the scenery are known
|
||||
if (false == m_drives_checked)
|
||||
{
|
||||
@@ -1374,7 +1421,9 @@ bool editor_mode::update()
|
||||
return true;
|
||||
}
|
||||
|
||||
steps.step("start");
|
||||
Timer::UpdateTimers(true);
|
||||
move_thrown_switches();
|
||||
|
||||
simulation::State.update_clocks();
|
||||
simulation::Environment.update();
|
||||
@@ -1389,6 +1438,9 @@ bool editor_mode::update()
|
||||
}
|
||||
|
||||
auto const deltarealtime = Timer::GetDeltaRenderTime();
|
||||
// the whole frame before, drawing included: one slow where its update wasn't took the time drawing
|
||||
if (deltarealtime > 0.15 && simulation::is_ready)
|
||||
WriteLog("Editor: slow frame before, " + std::to_string(static_cast<int>(deltarealtime * 1000.0)) + " ms in all", logtype::generic);
|
||||
|
||||
// reconcile camera fly-mode with the real right-button state. ImGui is always fed the button
|
||||
// events (even when it captures the mouse), so io.MouseDown[1] is authoritative; if a release
|
||||
@@ -1415,6 +1467,7 @@ bool editor_mode::update()
|
||||
m_input.mouse.button(GLFW_MOUSE_BUTTON_LEFT, GLFW_RELEASE);
|
||||
}
|
||||
terrain_history_step();
|
||||
steps.step("simulation");
|
||||
|
||||
// fixed step render time routines (50 Hz)
|
||||
fTime50Hz += deltarealtime; // accumulate even when paused to keep frame reads stable
|
||||
@@ -1445,8 +1498,10 @@ bool editor_mode::update()
|
||||
|
||||
}
|
||||
|
||||
steps.step("ui");
|
||||
// variable step routines
|
||||
update_camera(deltarealtime);
|
||||
steps.step("camera");
|
||||
|
||||
// drop the selection if its layer was hidden or locked in the meantime
|
||||
if (m_node != nullptr && false == scene::Layers.editable(m_node))
|
||||
@@ -1483,6 +1538,7 @@ bool editor_mode::update()
|
||||
audio::renderer.update(Global.iPause ? 0.0 : deltarealtime);
|
||||
|
||||
GfxRenderer->Update(deltarealtime);
|
||||
steps.step("renderer");
|
||||
|
||||
simulation::is_ready = true;
|
||||
|
||||
@@ -1498,24 +1554,26 @@ bool editor_mode::update()
|
||||
draw_terrain_overlay();
|
||||
|
||||
// --- geoportal orthophoto: streamed around the camera, drawn beneath the other overlays ---
|
||||
// far out the top view shows the base map in place of the imagery, and the ground isn't drawn under it
|
||||
{
|
||||
auto const &io{ImGui::GetIO()};
|
||||
auto const metres{Global.EditorOrtho && io.DisplaySize.y > 0.0f ? 2.0 * static_cast<double>(Global.EditorOrthoExtent) / io.DisplaySize.y : 0.0};
|
||||
auto const aspect{io.DisplaySize.y > 0.0f ? static_cast<double>(io.DisplaySize.x / io.DisplaySize.y) : 1.0};
|
||||
Global.EditorOrthoMap = m_orthophoto.enabled() && metres >= (m_orthophoto.overview() ? 1.6 : 2.0);
|
||||
m_orthophoto.overview(Global.EditorOrthoMap ? metres : 0.0, static_cast<double>(Global.EditorOrthoExtent) * std::sqrt(1.0 + aspect * aspect));
|
||||
}
|
||||
// chunks of the terrain come from the files and from the model: the imagery lying at guessed heights goes onto them
|
||||
// and so do the squares of the model it lies on meanwhile
|
||||
auto const resident{m_streamer.resident()};
|
||||
auto const waiting{m_nmt.waiting()};
|
||||
if (resident > m_ortho_resident || waiting < m_ortho_waiting)
|
||||
{
|
||||
m_orthophoto.ground_arrived();
|
||||
for (auto &layer : m_wms)
|
||||
layer->ground_arrived();
|
||||
}
|
||||
m_ortho_resident = resident;
|
||||
m_ortho_waiting = waiting;
|
||||
steps.step("terrain");
|
||||
m_orthophoto.update(Camera.Pos);
|
||||
steps.step("orthophoto");
|
||||
update_wms_layers();
|
||||
steps.step("wms");
|
||||
nmt_update();
|
||||
keep_nmt_settings();
|
||||
steps.step("nmt");
|
||||
// what's drawn over the 3d view stays in it, the docks around it are see-through a little
|
||||
auto *background{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
|
||||
background->PushClipRect(ImVec2(editor_ui::view_min().x, editor_ui::view_min().y), ImVec2(editor_ui::view_max().x, editor_ui::view_max().y), true);
|
||||
@@ -1523,6 +1581,7 @@ bool editor_mode::update()
|
||||
draw_nmt_pending();
|
||||
area_update();
|
||||
draw_download_area();
|
||||
steps.step("imagery");
|
||||
|
||||
// --- ImGuizmo: in-viewport transform gizmo for the selected node ---
|
||||
render_gizmo();
|
||||
@@ -1530,13 +1589,14 @@ bool editor_mode::update()
|
||||
render_viewport_overlays();
|
||||
|
||||
render_track_inspector();
|
||||
steps.step("inspector");
|
||||
if (ui()->mode() == nodebank_panel::TRACK)
|
||||
{
|
||||
draw_track_lines();
|
||||
draw_track_labels();
|
||||
draw_track_legend();
|
||||
}
|
||||
else if (Global.EditorOrtho && (Global.EditorOrthoTracks > 1 || Global.EditorOrthoMap))
|
||||
else if (Global.EditorOrtho && Global.EditorOrthoTracks > 1)
|
||||
draw_track_lines();
|
||||
if (selected_track())
|
||||
draw_track_overlay();
|
||||
@@ -1544,11 +1604,13 @@ bool editor_mode::update()
|
||||
draw_route_overlay();
|
||||
if (ui()->mode() == nodebank_panel::TRACK && m_track_tab == track_tab::straights)
|
||||
draw_straights_overlay();
|
||||
steps.step("track lines");
|
||||
if (ui()->mode() == nodebank_panel::TRACK)
|
||||
{
|
||||
if (m_track_box.active && false == ImGui::GetIO().MouseDown[0])
|
||||
finish_track_box();
|
||||
update_track_hover();
|
||||
steps.step("hover");
|
||||
point_drag_update();
|
||||
switch_slide_update();
|
||||
draw_switch_slide();
|
||||
@@ -1568,21 +1630,29 @@ bool editor_mode::update()
|
||||
m_vehicle.draw_overlay();
|
||||
}
|
||||
render_track_context();
|
||||
steps.step("context");
|
||||
update_build_tools();
|
||||
steps.step("laying");
|
||||
tape_update();
|
||||
update_track_intent();
|
||||
steps.step("intent");
|
||||
draw_track_hover();
|
||||
draw_track_set();
|
||||
draw_track_spread();
|
||||
draw_build_overlay();
|
||||
steps.step("laying drawn");
|
||||
draw_blade_tips();
|
||||
draw_track_intent();
|
||||
draw_tape();
|
||||
steps.step("track drawn");
|
||||
m_speed.update();
|
||||
steps.step("speed");
|
||||
m_joints.update();
|
||||
steps.step("joints");
|
||||
m_srk.update();
|
||||
kilometrage::follow(m_track_edits);
|
||||
m_km.update();
|
||||
steps.step("srk km");
|
||||
draw_track_hints();
|
||||
draw_empty_scenery_hint();
|
||||
draw_profile_overlay();
|
||||
@@ -1591,6 +1661,7 @@ bool editor_mode::update()
|
||||
m_joints.draw_overlay();
|
||||
m_srk.draw_overlay();
|
||||
m_km.draw_overlay();
|
||||
steps.step("overlays");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1608,6 +1679,7 @@ bool editor_mode::update()
|
||||
// the roads and the terrain are fields of work, their tools are drawn in the tool options window in place of the edit mode ones
|
||||
ui()->set_workspace(m_terrain_open ? editor_ui::workspace::terrain : m_roadtool.window ? editor_ui::workspace::roads : editor_ui::workspace::none);
|
||||
|
||||
steps.step("roads");
|
||||
// --- array: the one made last is kept in line with its settings ---
|
||||
update_array();
|
||||
|
||||
@@ -1623,11 +1695,11 @@ bool editor_mode::update()
|
||||
render_first_steps();
|
||||
render_nmt_prompt();
|
||||
render_nmt_banner();
|
||||
render_orthophoto_window();
|
||||
render_switch_designer();
|
||||
render_environment_window();
|
||||
draw_selection_overlay();
|
||||
render_context_menu();
|
||||
steps.step("windows");
|
||||
|
||||
// --- ImGui: Editor Settings & History windows ---
|
||||
if(m_settings_open)
|
||||
@@ -1796,6 +1868,10 @@ void editor_mode::render_terrain_ui()
|
||||
ImGui::SameLine();
|
||||
if (ImGui::Button(STR_C("Clear the selection")))
|
||||
m_terrain_selection.clear();
|
||||
if (ImGui::Button(STR_C("Remove the selected (Del)")))
|
||||
remove_selected_chunks();
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("Takes the selected chunks out of the terrain of the scenery, and out of its files on save; Ctrl+Z brings them back"));
|
||||
ImGui::Checkbox(STR_C("Paint every 0.5 m with 0.5 m spacing"), &m_terrain_convert_paint);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("Chunks converted to 0.5 m get their paint samples every 0.5 m as well (every metre otherwise),\n"
|
||||
@@ -2276,6 +2352,12 @@ void editor_mode::load_orthophoto_settings()
|
||||
if (scenery == m_orthophoto_scenery)
|
||||
return;
|
||||
m_orthophoto_scenery = scenery;
|
||||
// a scenery of its own: nobody chose for it yet, unless the user or the scenery keeps the orthophoto on or off
|
||||
m_orthophoto_chosen = false;
|
||||
// the height of the terrain model against the tracks of the scenery, as it was fitted or typed in last
|
||||
m_nmt_state.offset = 0.0;
|
||||
m_nmt_state.fitted = false;
|
||||
std::as_const(EditorSettings).terrain_model_offset(scenery, m_nmt_state.offset, m_nmt_state.fitted);
|
||||
|
||||
auto const &stored = EditorSettings.orthophoto();
|
||||
editor_orthophoto::config config = m_orthophoto.settings();
|
||||
@@ -2302,8 +2384,17 @@ void editor_mode::load_orthophoto_settings()
|
||||
{
|
||||
m_orthophoto_mark = marked.front();
|
||||
m_orthophoto.enabled(enabled);
|
||||
m_orthophoto_chosen = true;
|
||||
WriteLog(format("Editor: the map settings of the scenery, orthophoto %s", enabled ? "on" : "off"));
|
||||
}
|
||||
// the user's choice stands over the scenery's; turned on only where the scenery lies on the map, a fictional one has nothing to
|
||||
// show, and there it's left to the terrain model once the scenery is placed
|
||||
if (auto const on{stored.on}; on.has_value() && (false == *on || config.north != 0.0 || config.east != 0.0))
|
||||
{
|
||||
m_orthophoto.enabled(*on);
|
||||
m_orthophoto_chosen = true;
|
||||
WriteLog(format("Editor: the orthophoto %s, as the user left it", *on ? "on" : "off"));
|
||||
}
|
||||
m_orthophoto.settings(config);
|
||||
m_orthophoto_origin_edit = {config.north, config.east};
|
||||
// the area the imagery and the terrain model are downloaded in, none in a scenery which has none
|
||||
@@ -2330,6 +2421,42 @@ void editor_mode::save_orthophoto_settings()
|
||||
EditorSettings.save();
|
||||
}
|
||||
|
||||
void editor_mode::choose_orthophoto(bool const On)
|
||||
{
|
||||
m_orthophoto.enabled(On);
|
||||
m_orthophoto_chosen = true;
|
||||
EditorSettings.orthophoto().on = On;
|
||||
EditorSettings.save();
|
||||
}
|
||||
|
||||
void editor_mode::keep_nmt_settings()
|
||||
{
|
||||
// not while a number is being typed in, which changes it at every key
|
||||
if (ImGui::IsAnyItemActive())
|
||||
return;
|
||||
auto const &state{m_nmt_state};
|
||||
auto &stored{EditorSettings.terrain_model()};
|
||||
auto changed{stored.band != state.band || stored.around != m_terrain_add_count || stored.shape != state.shape || stored.follow != state.follow};
|
||||
stored.band = state.band;
|
||||
stored.around = m_terrain_add_count;
|
||||
stored.shape = state.shape;
|
||||
stored.follow = state.follow;
|
||||
// the offset of the model is the scenery's, fitted to its tracks or typed in
|
||||
if (false == m_orthophoto_scenery.empty())
|
||||
{
|
||||
double offset{0.0};
|
||||
bool fitted{false};
|
||||
std::as_const(EditorSettings).terrain_model_offset(m_orthophoto_scenery, offset, fitted);
|
||||
if (offset != state.offset || fitted != state.fitted)
|
||||
{
|
||||
EditorSettings.terrain_model_offset(m_orthophoto_scenery, state.offset, state.fitted);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
if (changed)
|
||||
EditorSettings.save();
|
||||
}
|
||||
|
||||
void editor_mode::render_orthophoto_ui()
|
||||
{
|
||||
load_orthophoto_settings();
|
||||
@@ -2338,11 +2465,6 @@ void editor_mode::render_orthophoto_ui()
|
||||
bool changed = false; // apply to the layer
|
||||
bool persist = false; // and store in the editor settings
|
||||
|
||||
bool enabled = m_orthophoto.enabled();
|
||||
if (ImGui::Checkbox(STR_C("Show orthophoto (geoportal.gov.pl)"), &enabled))
|
||||
m_orthophoto.enabled(enabled);
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted(STR_C("Scenery origin (0,0,0) in PUWG 1992 / EPSG:2180"));
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("PUWG 1992 (EPSG:2180): the coordinate system of the maps of Poland, in metres; the numbers as on geoportal.gov.pl"));
|
||||
@@ -2438,6 +2560,8 @@ void editor_mode::render_orthophoto_ui()
|
||||
ground_tiles.clear();
|
||||
forget_ground_shapes();
|
||||
m_orthophoto.refit();
|
||||
for (auto &layer : m_wms)
|
||||
layer->refit();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip(STR_C("Samples the ground again, after it was edited"));
|
||||
@@ -2532,7 +2656,7 @@ void editor_mode::render_orthophoto_ui()
|
||||
|
||||
void editor_mode::draw_orthophoto()
|
||||
{
|
||||
if (!m_orthophoto.enabled())
|
||||
if (false == m_orthophoto.enabled() && std::none_of(m_wms.begin(), m_wms.end(), [](auto const &Layer) { return Layer->enabled(); }))
|
||||
return;
|
||||
// the camera was just published to the renderer (update_camera), so build the view from it rather than
|
||||
// from the renderer's previous frame; camera-relative rotation, same projection as the other overlays
|
||||
@@ -2541,16 +2665,11 @@ void editor_mode::draw_orthophoto()
|
||||
glm::mat4 const view = glm::mat4(glm::mat3(viewmatrix));
|
||||
ImGuiIO const &io = ImGui::GetIO();
|
||||
float const aspect = io.DisplaySize.y > 0.0f ? io.DisplaySize.x / io.DisplaySize.y : 1.0f;
|
||||
m_orthophoto.draw(editor_mode::projection_matrix(aspect) * view, Camera.Pos, io.DisplaySize.x, io.DisplaySize.y);
|
||||
if (false == m_orthophoto.overview())
|
||||
return;
|
||||
char const *credit{STR_C("(c) OpenStreetMap contributors, geoportal.gov.pl")};
|
||||
auto const size{ImGui::CalcTextSize(credit)};
|
||||
auto const corner{editor_ui::view_max()};
|
||||
ImVec2 const from{corner.x - size.x - 12.0f, corner.y - size.y - 44.0f};
|
||||
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
|
||||
drawlist->AddRectFilled(ImVec2(from.x - 4.0f, from.y - 2.0f), ImVec2(from.x + size.x + 4.0f, from.y + size.y + 2.0f), IM_COL32(255, 255, 255, 190), 3.0f);
|
||||
drawlist->AddText(from, IM_COL32(40, 40, 40, 255), credit);
|
||||
auto const projection{editor_mode::projection_matrix(aspect) * view};
|
||||
m_orthophoto.draw(projection, Camera.Pos, io.DisplaySize.x, io.DisplaySize.y);
|
||||
// the WMS layers over it, the top one of the list last
|
||||
for (auto layer{m_wms.rbegin()}; layer != m_wms.rend(); ++layer)
|
||||
(*layer)->draw(projection, Camera.Pos, io.DisplaySize.x, io.DisplaySize.y);
|
||||
}
|
||||
|
||||
void editor_mode::draw_ortho_compass() const
|
||||
@@ -2604,6 +2723,18 @@ void editor_mode::forget_ground()
|
||||
forget_ground_shapes();
|
||||
}
|
||||
|
||||
void editor_mode::forget_ground_in(glm::dvec2 const &Min, glm::dvec2 const &Max)
|
||||
{
|
||||
for (auto tile{ground_tiles.begin()}; tile != ground_tiles.end();)
|
||||
{
|
||||
glm::dvec2 const low{tile->first.first * ground_tile_size, tile->first.second * ground_tile_size};
|
||||
if (low.x <= Max.x && low.x + ground_tile_size >= Min.x && low.y <= Max.y && low.y + ground_tile_size >= Min.y)
|
||||
tile = ground_tiles.erase(tile);
|
||||
else
|
||||
++tile;
|
||||
}
|
||||
}
|
||||
|
||||
editor_terrain *editor_mode::terrain_at(double X, double Z)
|
||||
{
|
||||
return m_streamer.terrain_at(X, Z);
|
||||
@@ -3422,6 +3553,25 @@ void editor_mode::fill_terrain_area()
|
||||
m_paint_outline.clear();
|
||||
}
|
||||
|
||||
void editor_mode::remove_selected_chunks()
|
||||
{
|
||||
// one step of the history, as the terrain changed in a frame goes
|
||||
std::size_t removed{0};
|
||||
for (auto const &key : m_terrain_selection)
|
||||
if (m_streamer.exists(key))
|
||||
{
|
||||
m_streamer.remove_chunk(key.first, key.second);
|
||||
++removed;
|
||||
}
|
||||
m_terrain_selection.clear();
|
||||
if (removed > 0)
|
||||
{
|
||||
ground_tiles.clear();
|
||||
forget_ground_shapes();
|
||||
}
|
||||
m_terrain_status = format(STR_P("%zu chunk removed, Ctrl+Z brings it back", "%zu chunks removed, Ctrl+Z brings them back", removed), removed);
|
||||
}
|
||||
|
||||
void editor_mode::convert_selected_chunks()
|
||||
{
|
||||
// with 0.5 m spacing the paint can be as fine, the other spacings keep a sample per metre
|
||||
@@ -3666,21 +3816,286 @@ void editor_mode::render_map_menu()
|
||||
return;
|
||||
bool enabled = m_orthophoto.enabled();
|
||||
if (ImGui::MenuItem(STR_C("Orthophoto"), nullptr, enabled))
|
||||
m_orthophoto.enabled(false == enabled);
|
||||
choose_orthophoto(false == enabled);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("Aerial imagery of geoportal.gov.pl under the scenery, laid out by the origin of the scenery"));
|
||||
ImGui::MenuItem(STR_C("Orthophoto settings..."), nullptr, &m_orthophoto_window);
|
||||
// the settings among the base layers in the layers window
|
||||
if (ImGui::MenuItem(STR_C("Orthophoto settings..."), nullptr, orthophoto_settings_open()))
|
||||
{
|
||||
ui()->show_layers();
|
||||
m_orthophoto_window = true;
|
||||
}
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
|
||||
void editor_mode::render_orthophoto_window()
|
||||
bool editor_mode::orthophoto_settings_open() const
|
||||
{
|
||||
if (false == m_orthophoto_window)
|
||||
return;
|
||||
ImGui::SetNextWindowSize(ImVec2(430.0f, 0.0f), ImGuiCond_FirstUseEver);
|
||||
if (ImGui::Begin((std::string(STR_C("Orthophoto")) + "###orthophoto").c_str(), &m_orthophoto_window))
|
||||
// drawn open in the layers window in this frame or in the last one
|
||||
return ImGui::GetCurrentContext() != nullptr && ImGui::GetFrameCount() - m_orthophoto_frame <= 1;
|
||||
}
|
||||
|
||||
void editor_mode::render_base_layers()
|
||||
{
|
||||
auto &stored{EditorSettings.wms_layers()};
|
||||
bool save{false};
|
||||
// the WMS layers of the user's, the top one first, each drawn over the ones under it
|
||||
int moved{-1};
|
||||
int removed{-1};
|
||||
for (int i = 0; i < static_cast<int>(stored.size()); ++i)
|
||||
{
|
||||
auto &entry{stored[static_cast<std::size_t>(i)]};
|
||||
ImGui::PushID(i + 1);
|
||||
save |= ImGui::Checkbox("##visible", &entry.visible);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("Show or hide the layer"));
|
||||
ImGui::SameLine();
|
||||
if (ImGui::TreeNode("##wms", "%s", entry.title.empty() ? entry.layers.c_str() : entry.title.c_str()))
|
||||
{
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextDisabled("%s", entry.url.c_str());
|
||||
ImGui::TextDisabled(STR_C("Layers: %s"), entry.layers.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::SetNextItemWidth(160.0f);
|
||||
ImGui::SliderFloat(STR_C("Opacity"), &entry.opacity, 0.0f, 1.0f, "%.2f");
|
||||
save |= ImGui::IsItemDeactivatedAfterEdit();
|
||||
if (false == entry.terms.empty())
|
||||
{
|
||||
ImGui::TextUnformatted(STR_C("Terms of the service:"));
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextDisabled("%s", entry.terms.c_str());
|
||||
ImGui::PopTextWrapPos();
|
||||
}
|
||||
if (i > 0)
|
||||
{
|
||||
if (ImGui::SmallButton(STR_C("Up")))
|
||||
moved = i - 1;
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (i + 1 < static_cast<int>(stored.size()))
|
||||
{
|
||||
if (ImGui::SmallButton(STR_C("Down")))
|
||||
moved = i;
|
||||
ImGui::SameLine();
|
||||
}
|
||||
if (ImGui::SmallButton(STR_C("Remove")))
|
||||
removed = i;
|
||||
ImGui::TreePop();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
// a layer swapped with the one under it, or one taken away; the ones of the view go along
|
||||
if (moved >= 0 && moved + 1 < static_cast<int>(stored.size()))
|
||||
{
|
||||
std::swap(stored[static_cast<std::size_t>(moved)], stored[static_cast<std::size_t>(moved) + 1]);
|
||||
if (moved + 1 < static_cast<int>(m_wms.size()))
|
||||
std::swap(m_wms[static_cast<std::size_t>(moved)], m_wms[static_cast<std::size_t>(moved) + 1]);
|
||||
save = true;
|
||||
}
|
||||
if (removed >= 0)
|
||||
{
|
||||
stored.erase(stored.begin() + removed);
|
||||
if (removed < static_cast<int>(m_wms.size()))
|
||||
{
|
||||
m_wms[static_cast<std::size_t>(removed)]->enabled(false);
|
||||
m_wms[static_cast<std::size_t>(removed)]->detach_scene();
|
||||
m_wms.erase(m_wms.begin() + removed);
|
||||
}
|
||||
save = true;
|
||||
}
|
||||
if (save)
|
||||
EditorSettings.save();
|
||||
|
||||
// the orthophoto at the bottom, its settings opened here from the map menu too
|
||||
ImGui::PushID("orthophoto");
|
||||
bool enabled{m_orthophoto.enabled()};
|
||||
if (ImGui::Checkbox("##visible", &enabled))
|
||||
choose_orthophoto(enabled);
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("Aerial imagery of geoportal.gov.pl under the scenery, laid out by the origin of the scenery"));
|
||||
ImGui::SameLine();
|
||||
if (m_orthophoto_window)
|
||||
{
|
||||
ImGui::SetNextItemOpen(true);
|
||||
m_orthophoto_window = false;
|
||||
}
|
||||
if (ImGui::TreeNode("##settings", "%s", STR_C("Orthophoto (geoportal.gov.pl)")))
|
||||
{
|
||||
m_orthophoto_frame = ImGui::GetFrameCount();
|
||||
render_orthophoto_ui();
|
||||
ImGui::End();
|
||||
ImGui::TreePop();
|
||||
}
|
||||
ImGui::PopID();
|
||||
|
||||
// the terrain of the model under the tracks laid, as the terrain panel has it too
|
||||
if (ImGui::Checkbox(STR_C("Terrain of the model right away while laying"), &m_nmt_state.grow))
|
||||
{
|
||||
EditorSettings.terrain_while_laying(m_nmt_state.grow);
|
||||
EditorSettings.save();
|
||||
}
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C("Where the terrain of the scenery ends, the terrain model is made under the tracks as they're laid, around the cursor from the first point placed; "
|
||||
"the chunks there are stay as they are. Off, it's made only when asked for"));
|
||||
|
||||
render_wms_adding();
|
||||
}
|
||||
|
||||
void editor_mode::render_wms_adding()
|
||||
{
|
||||
auto &adding{m_wms_adding};
|
||||
ImGui::PushID("wmsadding");
|
||||
if (false == ImGui::TreeNode("##add", "%s", STR_C("Add a WMS layer")))
|
||||
{
|
||||
ImGui::PopID();
|
||||
return;
|
||||
}
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextDisabled("%s", STR_C("The address of a WMS service drawing in PUWG 1992 (EPSG:2180). Its tiles are kept in memory only and asked for only around the camera; "
|
||||
"what the service allows is up to you, its terms are shown below"));
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
auto const entered{ImGui::InputTextWithHint("##url", "https://...", adding.url.data(), adding.url.size(), ImGuiInputTextFlags_EnterReturnsTrue)};
|
||||
std::string const url{adding.url.data()};
|
||||
if ((ImGui::Button(STR_C("Read the layers")) || entered) && false == adding.reading.valid() && false == url.empty())
|
||||
{
|
||||
adding.info.reset();
|
||||
adding.ticked.clear();
|
||||
adding.service = url;
|
||||
adding.reading = std::async(std::launch::async, [url]() { return editor_orthophoto::describe(url); });
|
||||
}
|
||||
if (adding.reading.valid())
|
||||
{
|
||||
if (adding.reading.wait_for(std::chrono::seconds(0)) == std::future_status::ready)
|
||||
{
|
||||
adding.info = adding.reading.get();
|
||||
adding.ticked.assign(adding.info->layers.size(), 0);
|
||||
}
|
||||
else
|
||||
ImGui::TextDisabled("%s", STR_C("Reading the capabilities of the service..."));
|
||||
}
|
||||
if (adding.info.has_value())
|
||||
{
|
||||
auto const &info{*adding.info};
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
if (false == info.error.empty())
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.45f, 0.4f, 1.0f), STR_C("The service can't be read: %s"), info.error.c_str());
|
||||
if (false == info.title.empty())
|
||||
ImGui::TextUnformatted(info.title.c_str());
|
||||
if (false == info.terms.empty())
|
||||
{
|
||||
ImGui::TextUnformatted(STR_C("Terms of the service:"));
|
||||
ImGui::TextDisabled("%s", info.terms.c_str());
|
||||
}
|
||||
if (false == info.puwg && false == info.layers.empty())
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.75f, 0.2f, 1.0f), "%s", STR_C("The service doesn't draw in PUWG 1992 (EPSG:2180), the editor can't lay it out"));
|
||||
ImGui::PopTextWrapPos();
|
||||
if (false == info.layers.empty())
|
||||
{
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
ImGui::InputTextWithHint("##filter", STR_C("Filter the layers"), adding.filter.data(), adding.filter.size());
|
||||
auto const filter{ToLower(adding.filter.data())};
|
||||
ImGui::BeginChild("##layers", ImVec2(0.0f, std::min(200.0f, ImGui::GetFrameHeightWithSpacing() * static_cast<float>(info.layers.size()) + 4.0f)), true);
|
||||
for (std::size_t i = 0; i < info.layers.size(); ++i)
|
||||
{
|
||||
auto const &[name, title]{info.layers[i]};
|
||||
auto const label{title.empty() || title == name ? name : title + " (" + name + ")"};
|
||||
if (false == filter.empty() && ToLower(label).find(filter) == std::string::npos)
|
||||
continue;
|
||||
bool ticked{adding.ticked[i] != 0};
|
||||
ImGui::PushID(static_cast<int>(i));
|
||||
if (ImGui::Checkbox(label.c_str(), &ticked))
|
||||
adding.ticked[i] = ticked ? 1 : 0;
|
||||
ImGui::PopID();
|
||||
}
|
||||
ImGui::EndChild();
|
||||
std::string layers;
|
||||
for (std::size_t i = 0; i < info.layers.size(); ++i)
|
||||
if (adding.ticked[i] != 0)
|
||||
layers += (layers.empty() ? "" : ",") + info.layers[i].first;
|
||||
auto const ready{info.puwg && false == layers.empty()};
|
||||
if (false == ready)
|
||||
{
|
||||
ImGui::PushItemFlag(ImGuiItemFlags_Disabled, true);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
|
||||
}
|
||||
auto const add{ImGui::Button(STR_C("Add")) && ready};
|
||||
if (false == ready)
|
||||
{
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopItemFlag();
|
||||
}
|
||||
if (add)
|
||||
{
|
||||
editorSettings::wms_layer entry;
|
||||
entry.url = adding.service;
|
||||
entry.layers = layers;
|
||||
entry.title = info.title.empty() ? layers : info.title;
|
||||
entry.terms = info.terms;
|
||||
// on top of the others
|
||||
EditorSettings.wms_layers().insert(EditorSettings.wms_layers().begin(), entry);
|
||||
EditorSettings.save();
|
||||
adding.info.reset();
|
||||
adding.ticked.clear();
|
||||
adding.url.fill('\0');
|
||||
adding.filter.fill('\0');
|
||||
}
|
||||
}
|
||||
}
|
||||
ImGui::TreePop();
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
void editor_mode::update_wms_layers()
|
||||
{
|
||||
auto const &stored{EditorSettings.wms_layers()};
|
||||
// a layer added or removed: the ones of the view made again, as the settings have them
|
||||
auto same{m_wms.size() == stored.size()};
|
||||
for (std::size_t i = 0; same && i < stored.size(); ++i)
|
||||
same = m_wms[i]->settings().service == stored[i].url && m_wms[i]->settings().layers == stored[i].layers;
|
||||
if (false == same)
|
||||
{
|
||||
for (auto &layer : m_wms)
|
||||
{
|
||||
layer->enabled(false);
|
||||
layer->detach_scene();
|
||||
}
|
||||
m_wms.clear();
|
||||
for (auto const &entry : stored)
|
||||
{
|
||||
auto layer{std::make_unique<editor_orthophoto>()};
|
||||
auto config{layer->settings()};
|
||||
config.service = entry.url;
|
||||
config.layers = entry.layers;
|
||||
config.hires = false;
|
||||
config.year = 0;
|
||||
config.in_scene = false;
|
||||
layer->settings(config);
|
||||
ground_sources(*layer);
|
||||
m_wms.push_back(std::move(layer));
|
||||
}
|
||||
}
|
||||
// laid out by the origin of the scenery and onto the ground as the orthophoto is, around the camera as far as it
|
||||
load_orthophoto_settings();
|
||||
auto const &base{m_orthophoto.settings()};
|
||||
auto const placed{base.north != 0.0 || base.east != 0.0};
|
||||
for (std::size_t i = 0; i < m_wms.size(); ++i)
|
||||
{
|
||||
auto &layer{*m_wms[i]};
|
||||
auto config{layer.settings()};
|
||||
if (config.north != base.north || config.east != base.east || config.radius != base.radius || config.drape != base.drape || config.lift != base.lift || config.height != base.height ||
|
||||
config.opacity != stored[i].opacity)
|
||||
{
|
||||
config.north = base.north;
|
||||
config.east = base.east;
|
||||
config.radius = base.radius;
|
||||
config.drape = base.drape;
|
||||
config.lift = base.lift;
|
||||
config.height = base.height;
|
||||
config.opacity = stored[i].opacity;
|
||||
layer.settings(config);
|
||||
}
|
||||
layer.enabled(stored[i].visible && placed);
|
||||
layer.update(Camera.Pos);
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::render_view_menu()
|
||||
@@ -3954,7 +4369,7 @@ void editor_mode::terrain_workspace(bool const Open)
|
||||
|
||||
void editor_mode::render_terrain_tool_options()
|
||||
{
|
||||
// the orthophoto has a window of its own, see render_orthophoto_window()
|
||||
// the orthophoto has its settings among the base layers in the layers window, see render_base_layers()
|
||||
render_terrain_ui();
|
||||
}
|
||||
|
||||
@@ -5182,15 +5597,15 @@ void editor_mode::render_first_steps()
|
||||
{"L", "The lay tool"},
|
||||
{"LMB", "A click on the ground or on a free end: where the track starts"},
|
||||
{"LMB", "Each next click: a bend of the line, the curve at it made by itself. The line shown isn't a track yet"},
|
||||
{"Enter", "Lays the track clicked; the clicks after it go on from its end"},
|
||||
{"Backspace", "Takes the last point back, then the last piece laid"},
|
||||
{"Enter", "Lays the track clicked; to go on from its end, click the end"},
|
||||
{"Backspace", "Takes the last point back"},
|
||||
{"Esc", "Ends laying; the points not laid yet go only with the second Esc"}},
|
||||
{{first_step::tracks, "Open the Tracks field of work"}, {first_step::lay, "Turn the lay tool on"}, {first_step::laid, "Click a few points and lay them with Enter"}},
|
||||
trial::lay},
|
||||
{"Curves",
|
||||
"curves",
|
||||
{{"LMB", "Click the bends of the line: each gets a curve as large as fits; one too sharp for the speed set in the panel has its label in orange"},
|
||||
{"Tab", "A curve seen on the photo: click points along its middle, the last two on the straight after it; one of two or three radii comes out a compound curve"},
|
||||
{"Tab", "A curve seen on the photo: click points along its middle, the curve ending at the last one; one of two or three radii comes out a compound curve"},
|
||||
{"Alt", "Held: the direction turns in steps of 15 degrees, or to 1:9, 1:6.6... to a straight track it starts by or crosses"},
|
||||
{"0 1 2 3", "Type the length of the next straight, Enter puts the point"},
|
||||
{"wheel", "Alone it zooms, as everywhere"}},
|
||||
@@ -5273,7 +5688,7 @@ void editor_mode::render_first_steps()
|
||||
{"F5", "The Terrain field of work: the terrain model (NMT) is in its tool options"},
|
||||
{"1", "Location: where the scenery lies, a click on the map or the place typed"},
|
||||
{"2", "Load along the tracks, or around the camera: the model comes from geoportal.gov.pl"},
|
||||
{"3", "Tracks onto the terrain: all the tracks go onto the model; the terrain to some tracks only: select them, Terrain in the inspector"},
|
||||
{"3", "Tracks onto the terrain: the tracks not fitted yet go onto the model, the ones fitted stay; the terrain to some tracks only: select them, Terrain in the inspector"},
|
||||
{"Ctrl + Z", "Takes the changes back"}},
|
||||
{{first_step::terrain, "Open the Terrain field of work"}, {first_step::nmt, "Load terrain from the model"}},
|
||||
trial::terrain,
|
||||
@@ -5537,6 +5952,7 @@ void editor_mode::render_shortcuts_window()
|
||||
{"Tracks",
|
||||
{{"Esc", "Select tool; cancels what's going on first"},
|
||||
{"L / T / U", "Lay track / switch / turntable"},
|
||||
{"H", "Throw the selected switch over to its other path, in the editor only"},
|
||||
{"G (C)", "Plan: a straight, its ends and the middle; a curve, its vertex and radius"},
|
||||
{"I / B", "Signalling / objects along the track"},
|
||||
{"P / V / J", "Profile / speed / joints"},
|
||||
@@ -5544,8 +5960,8 @@ void editor_mode::render_shortcuts_window()
|
||||
{"Shift+LMB", "Add a track to the selection, drag: a box"},
|
||||
{"RMB on a track", "What can be done with it"},
|
||||
{"K", "Split the selected track at the cursor, a break of the grade at the height of the ground"},
|
||||
{"L, LMB, LMB...", "Lay track: each click a bend of the straights, eased with a large curve; Enter lays them and goes on from the end"},
|
||||
{"Tab while laying", "The points clicked next go along the middle of a curve seen on the photo, the last two on the straight after it; Tab again: back to the bends"},
|
||||
{"L, LMB, LMB...", "Lay track: each click a bend of the straights, eased with a large curve; Enter lays them; a click on a free end goes on from it"},
|
||||
{"Tab while laying", "The points clicked next go along the middle of a curve seen on the photo, which ends at the last one; Tab again: back to the bends"},
|
||||
{"Drag from a free end", "Lead a track out of it: a straight, an arc and a straight where a curve is traced (Tab); onto another free end: join the two"},
|
||||
{"Ctrl+wheel", "Dragging from a free end: radius"},
|
||||
{"Shift+wheel", "Dragging over another free end: another way to join"},
|
||||
@@ -5553,7 +5969,7 @@ void editor_mode::render_shortcuts_window()
|
||||
{"Alt held while laying", "The direction in steps of 15 degrees, or 1:9, 1:6.6... to a straight track; the first click with Alt goes onto the axis of the track"},
|
||||
{"N", "Parallel track: LMB on a track, then the side and the spacing"},
|
||||
{"Shift+LMB on a track in L", "The Parallel tool (N) with it"},
|
||||
{"Enter / Backspace", "Laying track: lay the points clicked / take back the last point, then the last piece laid"},
|
||||
{"Enter / Backspace", "Laying track: lay the points clicked / take back the last point"},
|
||||
{"Esc twice while laying", "Drop the points clicked and not laid yet"},
|
||||
{"Del", "Delete the selected tracks"}}},
|
||||
{"Plan of a curve",
|
||||
@@ -6245,6 +6661,11 @@ void editor_mode::exit()
|
||||
m_fill_last.clear();
|
||||
m_orthophoto.cancel_pending();
|
||||
m_orthophoto.detach_scene(); // the layer is an editor aid, keep it out of the other modes
|
||||
for (auto &layer : m_wms)
|
||||
{
|
||||
layer->cancel_pending();
|
||||
layer->detach_scene();
|
||||
}
|
||||
// the download of the area held while the scenery is driven, it goes on once the editor is back
|
||||
m_orthophoto.area_paused(true);
|
||||
m_nmt.prefetch_paused(true);
|
||||
@@ -6513,7 +6934,10 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
|
||||
break;
|
||||
|
||||
case GLFW_KEY_DELETE:
|
||||
if (is_press(Action))
|
||||
// the chunks of the terrain selected, where the terrain is the field of work; else what's selected in the scenery
|
||||
if (is_press(Action) && m_terrain_open && false == m_terrain_selection.empty())
|
||||
remove_selected_chunks();
|
||||
else if (is_press(Action))
|
||||
delete_selected();
|
||||
break;
|
||||
|
||||
@@ -6563,6 +6987,12 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
|
||||
break;
|
||||
|
||||
case GLFW_KEY_TAB:
|
||||
// a switch dragged from a free end and bent: its diverging track the other way than the main one, or the same way
|
||||
if (is_press(Action) && false == anyModifier && m_switch.placing && m_switch.free && false == ImGui::GetIO().WantTextInput)
|
||||
{
|
||||
m_switch.one_sided = false == m_switch.one_sided;
|
||||
break;
|
||||
}
|
||||
// laying: the bends of straights or a curve traced, for the points clicked from now on
|
||||
if (is_press(Action) && false == anyModifier && ui()->mode() == nodebank_panel::TRACK && m_lay.active && false == ImGui::GetIO().WantTextInput)
|
||||
lay_switch_mode();
|
||||
@@ -6866,7 +7296,8 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
|
||||
finish_extend();
|
||||
if (m_switch.placing)
|
||||
{
|
||||
// a press let go with no drag places no switch, a click on the ground with no track there selects nothing
|
||||
// a press let go with no drag places no switch: the track it took is only selected, the ground with no track there
|
||||
// selects nothing
|
||||
auto const &io{ImGui::GetIO()};
|
||||
if (glm::length(glm::vec2{io.MousePos.x, io.MousePos.y} - m_switch.pressed_at) <= 10.0f)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user