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

feat(editor): laying track for a beginner: Ctrl+Z and Backspace during a chain of laid pieces go back to the end of the piece before instead of carrying on from a removed track (Ctrl+Y ends the chain); every warning of the laid shape and of a join is shown by the cursor for 4 s after the click, also when the radius chosen on the wheel didn't reach the cursor and the arc goes through it, the preview of a shape with a warning is orange as it is laid anyway; the switch type stays chosen after a switch is placed; the status says each click lays a piece; a "First steps" window (Help menu, opens with the editor until unticked) with eight steps ticked off as they are done; an empty scenery opens in Tracks with the camera over its origin and a grid where there is no ground under the cursor; F1 lists the laying gestures (Ctrl/Shift+wheel, drag from a free end, typed length); self-test command laychain; Polish translation

This commit is contained in:
maj00r
2026-10-12 01:21:50 +02:00
parent 866f626668
commit f28640dd44
9 changed files with 333 additions and 23 deletions

View File

@@ -931,6 +931,7 @@ void editor_mode::restore_terrain(EditorSnapshot &Snapshot)
void editor_mode::undo_last() void editor_mode::undo_last()
{ {
first_step_done(first_step::undo);
// a change of the terrain made a moment ago is a step of its own before it's taken back // a change of the terrain made a moment ago is a step of its own before it's taken back
m_history_busy = true; m_history_busy = true;
terrain_history_step(); terrain_history_step();
@@ -1478,6 +1479,7 @@ bool editor_mode::update()
render_new_scenery_popup(); render_new_scenery_popup();
render_open_scenery_popup(); render_open_scenery_popup();
render_shortcuts_window(); render_shortcuts_window();
render_first_steps();
render_orthophoto_window(); render_orthophoto_window();
render_environment_window(); render_environment_window();
draw_selection_overlay(); draw_selection_overlay();
@@ -2906,6 +2908,7 @@ bool editor_mode::save()
ui()->set_status(STR("The scenery wasn't opened for editing. Start the simulator with: -edit <scenery file>"), true); ui()->set_status(STR("The scenery wasn't opened for editing. Start the simulator with: -edit <scenery file>"), true);
return false; return false;
} }
first_step_done(first_step::save);
// the heightmap terrain is kept in files of its own, the scenery only needs the directive which loads it // the heightmap terrain is kept in files of its own, the scenery only needs the directive which loads it
std::vector<std::string> rootstatements; std::vector<std::string> rootstatements;
@@ -4582,9 +4585,66 @@ void editor_mode::render_help_menu()
ImGui::MenuItem(STR_C("Keyboard shortcuts"), "F1", &m_shortcuts_open); ImGui::MenuItem(STR_C("Keyboard shortcuts"), "F1", &m_shortcuts_open);
if (ImGui::IsItemHovered()) if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("The keys of the editor, for each field of work")); ImGui::SetTooltip("%s", STR_C("The keys of the editor, for each field of work"));
ImGui::MenuItem(STR_C("First steps"), nullptr, &m_first_steps_open);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Laying the first track, step by step"));
ImGui::EndMenu(); ImGui::EndMenu();
} }
void editor_mode::first_step_done(first_step const Step)
{
m_first_steps_done[static_cast<std::size_t>(Step)] = true;
}
void editor_mode::render_first_steps()
{
if (false == m_first_steps_open)
return;
if (ui()->mode() == nodebank_panel::TRACK)
first_step_done(first_step::tracks);
if (m_lay.active)
first_step_done(first_step::lay);
static char const *const steps[]{
"F3: the Tracks field of work",
"L: lay track",
"Click on the ground, then once more: a straight. Each next click lays a piece and goes on from its end, Esc ends",
"While laying on: Ctrl+wheel changes the radius, Shift+wheel the shape (arc then straight, straight then arc, arc, straight). The plain wheel zooms",
"Joining: press on a free end (magenta ring) and drag onto another free end",
"T: switch. Click a straight, then press on it and drag: along sets the direction, sideways the side",
"Ctrl+Z takes a step back, Backspace while laying takes back the last piece",
"Ctrl+S saves the scenery"};
ImGui::SetNextWindowSize(ImVec2(440.0f * Global.ui_scale, 0.0f), ImGuiCond_FirstUseEver);
ImGui::SetNextWindowPos(ImVec2(8.0f, ImGui::GetIO().DisplaySize.y - 8.0f), ImGuiCond_FirstUseEver, ImVec2(0.0f, 1.0f));
ImGui::SetNextWindowBgAlpha(1.0f);
if (ImGui::Begin((std::string(STR_C("First steps")) + "###firststeps").c_str(), &m_first_steps_open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize))
{
ImGui::PushTextWrapPos(420.0f * Global.ui_scale);
ImGui::TextDisabled("%s", STR_C("The ticks fill in by themselves, the window doesn't stop anything"));
for (std::size_t i = 0; i < m_first_steps_done.size(); ++i)
{
bool const done{m_first_steps_done[i]};
ImGui::TextColored(done ? ImVec4(0.4f, 0.9f, 0.4f, 1.0f) : ImGui::GetStyleColorVec4(ImGuiCol_Text), "%s", done ? "[x]" : "[ ]");
ImGui::SameLine();
if (done)
ImGui::TextDisabled("%s", STR_C(steps[i]));
else
ImGui::TextWrapped("%s", STR_C(steps[i]));
}
ImGui::Separator();
ImGui::TextDisabled("%s", STR_C("RMB held + W S A D: fly the camera, F: fly to the selected, F1: all the keys"));
if (ui()->mode() == nodebank_panel::TRACK && false == GfxRenderer->Mouse_Hit())
ImGui::TextColored(ImVec4(1.0f, 0.7f, 0.2f, 1.0f), "%s", STR_C("No ground under the cursor: track goes onto the level 0 plane, drawn as a grid. Terrain: F5"));
ImGui::PopTextWrapPos();
bool show{EditorSettings.first_steps()};
if (ImGui::Checkbox(STR_C("Show when the editor opens"), &show))
{
EditorSettings.first_steps(show);
EditorSettings.save();
}
}
ImGui::End();
}
void editor_mode::render_shortcuts_window() void editor_mode::render_shortcuts_window()
{ {
if (false == m_shortcuts_open) if (false == m_shortcuts_open)
@@ -4640,7 +4700,12 @@ void editor_mode::render_shortcuts_window()
{"Shift+LMB", "Add a track to the selection, drag: a box"}, {"Shift+LMB", "Add a track to the selection, drag: a box"},
{"RMB on a track", "What can be done with it"}, {"RMB on a track", "What can be done with it"},
{"K", "Split the selected track at the cursor"}, {"K", "Split the selected track at the cursor"},
{"Enter / Backspace", "Laying track: finish / take the last point back"}, {"L, LMB, LMB...", "Lay track: each click lays a piece and goes on from its end"},
{"Drag from a free end", "Lead a track out of it; onto another free end: join the two"},
{"Ctrl+wheel", "Laying: radius"},
{"Shift+wheel", "Laying: arc then straight, straight then arc, arc, straight; at a free end: another way to join"},
{"0-9 while laying", "Type the length (or the radius) of the next piece, Enter lays it"},
{"Enter / Backspace", "Laying track: lay up to the cursor / take the last piece back"},
{"Del", "Delete the selected tracks"}}}, {"Del", "Delete the selected tracks"}}},
{"Roads", {"Roads",
{{"Esc", "Finish the road being built"}, {{"Esc", "Finish the road being built"},
@@ -5150,6 +5215,19 @@ void editor_mode::enter()
WriteLog("Editor: camera at the first track, " + track->name()); WriteLog("Editor: camera at the first track, " + track->name());
break; break;
} }
if (false == m_saved_camera.has_value() && simulation::Paths.sequence().empty())
{
std::vector<char> found;
auto const ground{ground_heights({glm::dvec3{0.0, 5000.0, 0.0}}, true, &found).front()};
Camera.Pos = glm::dvec3{0.0, (found.front() != 0 ? ground : 0.0) + 60.0, 60.0};
Camera.Angle = glm::vec3(glm::radians(-45.0f), 0.0f, 0.0f);
Camera.m_owner = nullptr;
FreeFlyModeFlag = true;
Global.pCamera = Camera;
choose_work_area(work_area::tracks);
WriteLog("Editor: camera over the origin of the empty scenery");
}
m_first_steps_open = EditorSettings.first_steps();
} }
Global.ControlPicking = true; Global.ControlPicking = true;
@@ -5458,11 +5536,7 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
if (is_press(Action) && ui()->mode() == nodebank_panel::FILL && !m_fill_points.empty()) if (is_press(Action) && ui()->mode() == nodebank_panel::FILL && !m_fill_points.empty())
m_fill_points.pop_back(); m_fill_points.pop_back();
if (is_press(Action) && ui()->mode() == nodebank_panel::TRACK && m_lay.active && !m_lay.points.empty()) if (is_press(Action) && ui()->mode() == nodebank_panel::TRACK && m_lay.active && !m_lay.points.empty())
{ lay_back();
m_lay.points.pop_back();
if (m_lay.points.empty())
m_lay.start = {};
}
break; break;
case GLFW_KEY_ENTER: case GLFW_KEY_ENTER:

View File

@@ -252,6 +252,11 @@ class editor_mode : public application_mode, private editor_track::observer
void render_help_menu(); void render_help_menu();
void render_shortcuts_window(); void render_shortcuts_window();
bool m_shortcuts_open{false}; bool m_shortcuts_open{false};
enum class first_step { tracks, lay, laid, wheel, join, turnout, undo, save, count };
void render_first_steps();
void first_step_done(first_step const Step);
bool m_first_steps_open{false};
std::array<bool, static_cast<std::size_t>(first_step::count)> m_first_steps_done{};
// deletes what's selected: a road piece or point, a signal, an include of a scenery template, a model, tracks (Del) // deletes what's selected: a road piece or point, a signal, an include of a scenery template, a model, tracks (Del)
void delete_selected(); void delete_selected();
void delete_model(TAnimModel *Model, bool const Joined); void delete_model(TAnimModel *Model, bool const Joined);
@@ -1092,8 +1097,17 @@ class editor_mode : public application_mode, private editor_track::observer
bool copy_selected{false}; bool copy_selected{false};
editor_track::path_style style; editor_track::path_style style;
std::string status; std::string status;
double status_time{-100.0};
struct step
{
editor_track::snap_target start;
glm::dvec3 point{0.0};
};
std::vector<step> chain;
}; };
lay_tool m_lay; lay_tool m_lay;
void lay_after_history(bool const Undo);
void lay_back();
void render_lay_ui(); void render_lay_ui();
void render_lay_status(); void render_lay_status();
void lay_click(); void lay_click();

View File

@@ -1711,6 +1711,43 @@ editor_mode::selftest_result editor_mode::selftest_join(std::string const &comma
WriteLog(format("SELFTEST %s end: %.3f m from the target", command.c_str(), geometry::plan_distance(previous, b))); WriteLog(format("SELFTEST %s end: %.3f m from the target", command.c_str(), geometry::plan_distance(previous, b)));
return selftest_result::handled; return selftest_result::handled;
} }
if (command == "laychain")
{
int layout{0};
words >> layout >> m_lay.radius;
m_track_layout = static_cast<track_layout>(std::clamp(layout, 0, 3));
m_lay.active = true;
lay_cancel();
auto const report = [&](std::string const &Step) {
auto const *start{m_lay.start.track};
WriteLog(format("SELFTEST laychain %s: chain %zu, points %zu, start %s%s at %.2f %.2f, history %zu, status %s", Step.c_str(), m_lay.chain.size(), m_lay.points.size(), start != nullptr ? start->name().c_str() : "-", start != nullptr && start->m_editorremoved ? " (removed)" : "",
m_lay.points.empty() ? 0.0 : m_lay.points.front().x, m_lay.points.empty() ? 0.0 : m_lay.points.front().z, m_history.size(), m_lay.status.c_str()));
};
for (std::string word; words >> word;)
{
if (word == "b")
lay_back();
else if (word == "u")
undo_last();
else if (word == "r")
redo_last();
else
{
glm::dvec3 point{std::strtod(word.c_str(), nullptr), 5000.0, 0.0};
words >> point.z;
std::vector<char> found;
auto const ground{ground_heights({point}, true, &found)};
point.y = found[0] != 0 ? ground[0] : m_lay.points.empty() ? 0.0 : m_lay.points.back().y;
m_lay.mouse_snap = editor_track::find_free_end(nullptr, 1, point, 3.0, {});
m_lay.mouse = m_lay.mouse_snap.track != nullptr ? point : lay_bed(point);
lay_click();
word += format(" %.1f", point.z);
}
report(word);
}
lay_cancel();
return selftest_result::handled;
}
if (command == "layclicks") if (command == "layclicks")
{ {
int layout{0}; int layout{0};

View File

@@ -77,6 +77,27 @@ double piece_plan_radius(segment_data const &Path)
return curve.plan_length() / turn; return curve.plan_length() / turn;
} }
std::string lines_of(std::vector<std::string> const &Texts)
{
std::string text;
for (auto const &line : Texts)
text += (text.empty() ? "" : "\n") + line;
return text;
}
std::string narrowed(std::vector<segment_data> const &Pieces, editor_track::layout const Layout, double const Radius)
{
if (Layout != editor_track::layout::arc_straight && Layout != editor_track::layout::straight_arc)
return {};
double sharpest{0.0};
for (auto const &piece : Pieces)
if (auto const radius{piece_plan_radius(piece)}; radius >= 0.5 && (sharpest == 0.0 || radius < sharpest))
sharpest = radius;
if (sharpest == 0.0 || sharpest >= Radius * 0.99)
return {};
return format(STR_C("R %.0f m doesn't reach the cursor, the arc goes through it with R %.0f m"), Radius, sharpest);
}
std::string describe(editor_track::straight const &Line) std::string describe(editor_track::straight const &Line)
{ {
return format("L %.2f m az %.4f deg i %.2f per mille %zu paths", Line.length, Line.azimuth, Line.grade * 1000.0, Line.tracks.size()); return format("L %.2f m az %.4f deg i %.2f per mille %zu paths", Line.length, Line.azimuth, Line.grade * 1000.0, Line.tracks.size());
@@ -489,6 +510,7 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve
} }
} }
lay_after_history(Undo);
m_track_snap = {}; m_track_snap = {};
m_track_dirty = false; m_track_dirty = false;
m_track_point = {}; m_track_point = {};
@@ -531,6 +553,7 @@ void editor_mode::show_track_tab(track_tab const Tab)
m_straights.tool = 0; m_straights.tool = 0;
m_lay.points.clear(); m_lay.points.clear();
m_lay.start = {}; m_lay.start = {};
m_lay.chain.clear();
m_lay.preview.clear(); m_lay.preview.clear();
m_lay.status.clear(); m_lay.status.clear();
m_lay.active = Tab == track_tab::lay; m_lay.active = Tab == track_tab::lay;
@@ -614,7 +637,7 @@ std::string editor_mode::track_mode_name() const
return STR_C("Select: LMB on a path"); return STR_C("Select: LMB on a path");
switch (m_track_tab) switch (m_track_tab)
{ {
case track_tab::lay: return m_extend.active ? STR_C("Laying track: new path from the free end") : STR_C("Laying track: LMB adds the points"); case track_tab::lay: return m_extend.active ? STR_C("Laying track: new path from the free end") : STR_C("Laying track: each click lays a piece, Esc ends");
case track_tab::turnout: case track_tab::turnout:
if (m_switch.placing) if (m_switch.placing)
return STR_C("Switch: drag to set its direction and side"); return STR_C("Switch: drag to set its direction and side");
@@ -3636,7 +3659,7 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
hints = {{"LMB", chained ? "lay the shape up to here and go on from its end" : "lay the straight up to here and go on from its end"}, {"LMB on a free end", "join and lay"}}; hints = {{"LMB", chained ? "lay the shape up to here and go on from its end" : "lay the straight up to here and go on from its end"}, {"LMB on a free end", "join and lay"}};
if (chained || m_lay.mouse_snap.track != nullptr) if (chained || m_lay.mouse_snap.track != nullptr)
hints.insert(hints.end(), {{"Ctrl+wheel", "radius"}, {"Shift+wheel", "arc / straight, at a free end: another shape"}}); hints.insert(hints.end(), {{"Ctrl+wheel", "radius"}, {"Shift+wheel", "arc / straight, at a free end: another shape"}});
hints.insert(hints.end(), {{"Enter", "lay up to the cursor"}, {"Backspace", "take back the point"}, {"Esc", "cancel"}}); hints.insert(hints.end(), {{"Enter", "lay up to the cursor"}, {"Backspace", m_lay.chain.empty() ? "take back the point" : "take back the last piece"}, {"Esc", "cancel"}});
} }
} }
else if (false == m_straights.detour.empty()) else if (false == m_straights.detour.empty())
@@ -3821,6 +3844,8 @@ std::string editor_mode::track_readout() const
text += "\n" + lay.preview_error; text += "\n" + lay.preview_error;
if (lay.points.size() == 1 && (lay.start.track != nullptr || lay.mouse_snap.track != nullptr)) if (lay.points.size() == 1 && (lay.start.track != nullptr || lay.mouse_snap.track != nullptr))
text += "\n" + track_shaping(lay.mouse_snap.track != nullptr && lay.start.track != nullptr); text += "\n" + track_shaping(lay.mouse_snap.track != nullptr && lay.start.track != nullptr);
if (false == lay.status.empty() && ImGui::GetTime() - lay.status_time < 4.0)
text += "\n" + lay.status;
return text; return text;
} }
auto const &state{m_straights}; auto const &state{m_straights};
@@ -4449,7 +4474,13 @@ std::vector<segment_data> editor_mode::join_pieces(TTrack const *From, editor_tr
result = std::move(*eased); result = std::move(*eased);
} }
Length = result.length; Length = result.length;
Error = result.kind == alignment::join_kind::bezier ? std::string{STR_C("No curve fits between the ends, they are joined with a free curve")} : std::string{}; std::vector<std::string> notes;
for (auto const &warning : result.warnings)
if (m_join_radius > 0.0 || warning.find("R reduced") == std::string::npos)
notes.push_back(warning);
if (result.kind == alignment::join_kind::bezier)
notes.insert(notes.begin(), STR_C("No curve fits between the ends, they are joined with a free curve"));
Error = lines_of(notes);
m_join_last = result; m_join_last = result;
return result.pieces; return result.pieces;
} }
@@ -4514,6 +4545,9 @@ void editor_mode::finish_extend()
editor_track::commit(created); editor_track::commit(created);
digitize_drawn(created); digitize_drawn(created);
push_track_snapshot({}, created); push_track_snapshot({}, created);
first_step_done(first_step::laid);
if (target.track != nullptr)
first_step_done(first_step::join);
m_node = created.back(); m_node = created.back();
ui()->set_node(m_node); ui()->set_node(m_node);
straight_refresh(); straight_refresh();
@@ -4621,6 +4655,7 @@ bool editor_mode::track_wheel(double const Offset)
return *radius; return *radius;
return radii[0]; return radii[0];
}; };
first_step_done(first_step::wheel);
if (io.KeyCtrl) if (io.KeyCtrl)
{ {
if (false == joined) if (false == joined)
@@ -4662,6 +4697,7 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
{ {
auto const grade{-grade_of(tool.start.direction)}; auto const grade{-grade_of(tool.start.direction)};
pieces = editor_track::layout_pieces(m_track_layout, Points[0], glm::normalize(leaving), grade, Points[1], tool.radius); pieces = editor_track::layout_pieces(m_track_layout, Points[0], glm::normalize(leaving), grade, Points[1], tool.radius);
Error = narrowed(pieces, m_track_layout, tool.radius);
follow_ground(pieces, Points[0].y, grade, Points[1].y, std::numeric_limits<double>::quiet_NaN(), tool.start.track); follow_ground(pieces, Points[0].y, grade, Points[1].y, std::numeric_limits<double>::quiet_NaN(), tool.start.track);
} }
else if (End.track == nullptr) else if (End.track == nullptr)
@@ -4675,6 +4711,7 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
auto const back{editor_track::layout_pieces(m_track_layout, End.position, glm::normalize(-plan_of(End.direction)), -grade_of(End.direction), Points[0], tool.radius)}; auto const back{editor_track::layout_pieces(m_track_layout, End.position, glm::normalize(-plan_of(End.direction)), -grade_of(End.direction), Points[0], tool.radius)};
for (auto piece{back.rbegin()}; piece != back.rend(); ++piece) for (auto piece{back.rbegin()}; piece != back.rend(); ++piece)
pieces.push_back(reversed(*piece)); pieces.push_back(reversed(*piece));
Error = narrowed(pieces, m_track_layout, tool.radius);
follow_ground(pieces, Points[0].y, std::numeric_limits<double>::quiet_NaN(), End.position.y, grade_of(End.direction), End.track); follow_ground(pieces, Points[0].y, std::numeric_limits<double>::quiet_NaN(), End.position.y, grade_of(End.direction), End.track);
} }
for (auto const &piece : pieces) for (auto const &piece : pieces)
@@ -4697,7 +4734,7 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
Error = shape.errors.empty() ? std::string{STR_C("The track can't be laid through these points")} : shape.errors.front(); Error = shape.errors.empty() ? std::string{STR_C("The track can't be laid through these points")} : shape.errors.front();
return {}; return {};
} }
Error = shape.warnings.empty() ? std::string{} : shape.warnings.front(); Error = lines_of(shape.warnings);
auto const count = [&](alignment::result const &Shape, alignment::design const &Design) { auto const count = [&](alignment::result const &Shape, alignment::design const &Design) {
return std::max(alignment::minimum_pieces(Shape, Design), static_cast<std::size_t>(std::ceil(Shape.length / std::max(5.0, tool.piece_length)))); return std::max(alignment::minimum_pieces(Shape, Design), static_cast<std::size_t>(std::ceil(Shape.length / std::max(5.0, tool.piece_length))));
}; };
@@ -4734,7 +4771,7 @@ std::vector<segment_data> editor_mode::lay_pieces(std::vector<glm::dvec3> const
if (easedshape.valid) if (easedshape.valid)
{ {
Length = editor_track::eased_length(in, easedshape.length, out); Length = editor_track::eased_length(in, easedshape.length, out);
Error = easedshape.warnings.empty() ? std::string{} : easedshape.warnings.front(); Error = lines_of(easedshape.warnings);
return editor_track::eased_pieces(in, alignment::pieces(easedshape, easeddesign, count(easedshape, easeddesign)), out); return editor_track::eased_pieces(in, alignment::pieces(easedshape, easeddesign, count(easedshape, easeddesign)), out);
} }
Error = STR("The curvature changes abruptly where the track joins, there's no room for a transition curve") + (easedshape.errors.empty() ? std::string{} : ": " + easedshape.errors.front()); Error = STR("The curvature changes abruptly where the track joins, there's no room for a transition curve") + (easedshape.errors.empty() ? std::string{} : ": " + easedshape.errors.front());
@@ -4803,6 +4840,7 @@ void editor_mode::lay_click()
} }
if (tool.points.empty()) if (tool.points.empty())
{ {
tool.chain.clear();
tool.start = snap; tool.start = snap;
tool.points.push_back(snap.track != nullptr ? snap.position : tool.mouse); tool.points.push_back(snap.track != nullptr ? snap.position : tool.mouse);
return; return;
@@ -4813,6 +4851,7 @@ void editor_mode::lay_click()
m_typed.clear(); m_typed.clear();
if (tool.points.size() == 1) if (tool.points.size() == 1)
{ {
lay_tool::step const previous{tool.start, tool.points.front()};
tool.points.push_back(tool.mouse); tool.points.push_back(tool.mouse);
lay_finish({}); lay_finish({});
if (false == tool.points.empty()) if (false == tool.points.empty())
@@ -4830,6 +4869,7 @@ void editor_mode::lay_click()
next.position = path.points[segment_data::point::end]; next.position = path.points[segment_data::point::end];
auto const &control{path.points[segment_data::point::control2]}; auto const &control{path.points[segment_data::point::control2]};
next.direction = glm::normalize(control != glm::dvec3{} ? control : path.points[segment_data::point::start] - next.position); next.direction = glm::normalize(control != glm::dvec3{} ? control : path.points[segment_data::point::start] - next.position);
tool.chain.push_back(previous);
tool.start = next; tool.start = next;
tool.points = {next.position}; tool.points = {next.position};
return; return;
@@ -4843,6 +4883,7 @@ void editor_mode::lay_finish(editor_track::snap_target const &End)
double length; double length;
std::string error; std::string error;
auto const pieces{lay_pieces(tool.points, End, length, error)}; auto const pieces{lay_pieces(tool.points, End, length, error)};
tool.status_time = ImGui::GetTime();
if (pieces.empty()) if (pieces.empty())
{ {
tool.status = error.empty() ? std::string{STR_C("Click at least two points")} : error; tool.status = error.empty() ? std::string{STR_C("Click at least two points")} : error;
@@ -4859,6 +4900,9 @@ void editor_mode::lay_finish(editor_track::snap_target const &End)
editor_track::commit(created); editor_track::commit(created);
digitize_drawn(created); digitize_drawn(created);
push_track_snapshot({}, created); push_track_snapshot({}, created);
first_step_done(first_step::laid);
if (End.track != nullptr)
first_step_done(first_step::join);
tool.status = format(STR_C("Laid %zu paths, %.1f m"), created.size(), length); tool.status = format(STR_C("Laid %zu paths, %.1f m"), created.size(), length);
if (false == error.empty()) if (false == error.empty())
tool.status += "\n" + error; tool.status += "\n" + error;
@@ -4921,9 +4965,49 @@ void editor_mode::lay_cancel()
auto &tool{m_lay}; auto &tool{m_lay};
tool.points.clear(); tool.points.clear();
tool.start = {}; tool.start = {};
tool.chain.clear();
tool.preview.clear(); tool.preview.clear();
} }
void editor_mode::lay_after_history(bool const Undo)
{
auto &tool{m_lay};
if (tool.points.empty())
return;
if (false == Undo)
{
lay_cancel();
return;
}
while (tool.start.track != nullptr && tool.start.track->m_editorremoved)
{
if (tool.chain.empty())
{
lay_cancel();
return;
}
tool.start = tool.chain.back().start;
tool.points = {tool.chain.back().point};
tool.chain.pop_back();
}
}
void editor_mode::lay_back()
{
auto &tool{m_lay};
if (false == tool.chain.empty())
{
undo_last();
tool.status = STR_C("The last piece was taken back");
tool.status_time = ImGui::GetTime();
return;
}
if (false == tool.points.empty())
tool.points.pop_back();
if (tool.points.empty())
tool.start = {};
}
void editor_mode::render_lay_ui() void editor_mode::render_lay_ui()
{ {
auto &tool{m_lay}; auto &tool{m_lay};
@@ -5136,8 +5220,8 @@ void editor_mode::switch_placed(std::vector<TTrack *> const &Created)
if (found == Created.end()) if (found == Created.end())
return; return;
auto const slip{std::count_if(Created.begin(), Created.end(), [](TTrack const *Track) { return Track != nullptr && Track->eType == tt_Switch; }) > 1}; auto const slip{std::count_if(Created.begin(), Created.end(), [](TTrack const *Track) { return Track != nullptr && Track->eType == tt_Switch; }) > 1};
m_switch.armed = -1;
m_switch.placing = false; m_switch.placing = false;
first_step_done(first_step::turnout);
m_track_point = {}; m_track_point = {};
m_node = *found; m_node = *found;
ui()->set_node(m_node); ui()->set_node(m_node);
@@ -5319,6 +5403,27 @@ void editor_mode::draw_build_overlay() const
previous = next; previous = next;
} }
}; };
if (false == GfxRenderer->Mouse_Hit() && false == ImGui::GetIO().WantCaptureMouse)
{
auto const centre{m_lay.active ? m_lay.mouse : cursor_ground()};
double const cell{10.0};
int const reach{10};
glm::dvec3 const origin{std::round(centre.x / cell) * cell, centre.y, std::round(centre.z / cell) * cell};
for (int i = -reach; i <= reach; ++i)
{
auto const major{std::fmod(std::abs(origin.x / cell + i), 10.0) < 0.5};
auto const colour{major ? IM_COL32(255, 255, 255, 70) : IM_COL32(255, 255, 255, 35)};
for (int j = -reach; j < reach; ++j)
projection.line(drawlist, origin + glm::dvec3{i * cell, 0.0, j * cell}, origin + glm::dvec3{i * cell, 0.0, (j + 1) * cell}, colour, 1.0f);
}
for (int j = -reach; j <= reach; ++j)
{
auto const major{std::fmod(std::abs(origin.z / cell + j), 10.0) < 0.5};
auto const colour{major ? IM_COL32(255, 255, 255, 70) : IM_COL32(255, 255, 255, 35)};
for (int i = -reach; i < reach; ++i)
projection.line(drawlist, origin + glm::dvec3{i * cell, 0.0, j * cell}, origin + glm::dvec3{(i + 1) * cell, 0.0, j * cell}, colour, 1.0f);
}
}
auto const &display{ImGui::GetIO().DisplaySize}; auto const &display{ImGui::GetIO().DisplaySize};
for (auto const *track : simulation::Paths.sequence()) for (auto const *track : simulation::Paths.sequence())
{ {
@@ -5377,7 +5482,7 @@ void editor_mode::draw_build_overlay() const
auto const &lay{m_lay}; auto const &lay{m_lay};
auto const piece_colour = [&](segment_data const &Path) { auto const piece_colour = [&](segment_data const &Path) {
if (false == lay.preview_error.empty()) if (false == lay.preview_error.empty())
return overlay_color::invalid; return overlay_color::warning;
return piece_plan_radius(Path) < 0.5 ? IM_COL32(255, 255, 255, 230) : overlay_color::marked; return piece_plan_radius(Path) < 0.5 ? IM_COL32(255, 255, 255, 230) : overlay_color::marked;
}; };
for (auto const &piece : lay.preview) for (auto const &piece : lay.preview)

View File

@@ -216,7 +216,7 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
return make(kind::lay_end, lay.points.empty() ? "Click: start from this free end, drag: lead a track out of it" : "Click: join this end and lay"); return make(kind::lay_end, lay.points.empty() ? "Click: start from this free end, drag: lead a track out of it" : "Click: join this end and lay");
} }
intent.position = lay.mouse; intent.position = lay.mouse;
return make(kind::lay, lay.points.empty() ? "Click: start of the new track" : "Click: next point"); return make(kind::lay, lay.points.empty() ? "Click: start of the new track" : "Click: lay the track to here");
} }
if (m_track_tab == track_tab::turntable) if (m_track_tab == track_tab::turntable)
return turntable_intent(ground, shift); return turntable_intent(ground, shift);
@@ -369,7 +369,7 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
{ {
case track_tab::straights: return make(kind::select, "Click: the whole straight through this track"); case track_tab::straights: return make(kind::select, "Click: the whole straight through this track");
case track_tab::route: return make(kind::select, "Click: the curve of this track with its straights"); case track_tab::route: return make(kind::select, "Click: the curve of this track with its straights");
case track_tab::turnout: return make(kind::select, m_hover.track->eType == tt_Switch ? "Click: edit this switch" : m_switch.armed < 0 ? "Click: choose this track, then the type of the switch" : "Click: put a switch into this track"); case track_tab::turnout: return make(kind::select, m_hover.track->eType == tt_Switch ? "Click: edit this switch" : m_switch.armed < 0 ? "Click: choose this track, then the type of the switch" : "Click: choose this track, then press and drag on it for the switch");
case track_tab::profile: return make(kind::select, "Click: the grade line through this track"); case track_tab::profile: return make(kind::select, "Click: the grade line through this track");
case track_tab::lineside: return make(kind::select, "Click: the line the objects go along"); case track_tab::lineside: return make(kind::select, "Click: the line the objects go along");
default: return make(kind::select, "Click: select"); default: return make(kind::select, "Click: select");

View File

@@ -23,6 +23,7 @@ ImU32 constexpr marked{IM_COL32(255, 210, 60, 255)};
ImU32 constexpr grip{IM_COL32(60, 230, 90, 255)}; ImU32 constexpr grip{IM_COL32(60, 230, 90, 255)};
ImU32 constexpr selected{IM_COL32(40, 220, 255, 255)}; ImU32 constexpr selected{IM_COL32(40, 220, 255, 255)};
ImU32 constexpr invalid{IM_COL32(255, 60, 60, 255)}; ImU32 constexpr invalid{IM_COL32(255, 60, 60, 255)};
ImU32 constexpr warning{IM_COL32(255, 140, 40, 255)};
ImU32 constexpr highlight{IM_COL32(255, 255, 255, 255)}; ImU32 constexpr highlight{IM_COL32(255, 255, 255, 255)};
} // namespace overlay_color } // namespace overlay_color

View File

@@ -102,6 +102,8 @@ bool editorSettings::load()
parse(value, m_nodebank_card); parse(value, m_nodebank_card);
else if (key == "colours") else if (key == "colours")
parse(value, m_palette); parse(value, m_palette);
else if (key == "first_steps")
m_first_steps = (value == "1");
else if (key.rfind(origin_prefix, 0) == 0) else if (key.rfind(origin_prefix, 0) == 0)
{ {
// northing;easting // northing;easting
@@ -145,6 +147,7 @@ bool editorSettings::save()
stream << "nodebank_previews " << (m_nodebank_previews ? 1 : 0) << "\n"; stream << "nodebank_previews " << (m_nodebank_previews ? 1 : 0) << "\n";
stream << "nodebank_card " << m_nodebank_card << "\n"; stream << "nodebank_card " << m_nodebank_card << "\n";
stream << "colours " << m_palette << "\n"; stream << "colours " << m_palette << "\n";
stream << "first_steps " << (m_first_steps ? 1 : 0) << "\n";
stream << std::fixed << std::setprecision(3); stream << std::fixed << std::setprecision(3);
for (auto const &origin : m_orthophoto_origins) for (auto const &origin : m_orthophoto_origins)
stream << origin.first << " " << origin.second.first << ";" << origin.second.second << "\n"; stream << origin.first << " " << origin.second.first << ";" << origin.second.second << "\n";

View File

@@ -54,6 +54,9 @@ public:
int palette() const { return m_palette; } int palette() const { return m_palette; }
void palette(int Palette) { m_palette = Palette; } void palette(int Palette) { m_palette = Palette; }
bool first_steps() const { return m_first_steps; }
void first_steps(bool Show) { m_first_steps = Show; }
private: private:
movement_scheme m_movement{movement_scheme::wsad}; movement_scheme m_movement{movement_scheme::wsad};
orthophoto_settings m_orthophoto; orthophoto_settings m_orthophoto;
@@ -63,6 +66,7 @@ private:
bool m_nodebank_previews{true}; bool m_nodebank_previews{true};
int m_nodebank_card{96}; int m_nodebank_card{96};
int m_palette{0}; int m_palette{0};
bool m_first_steps{true};
}; };
// global editor settings instance // global editor settings instance

View File

@@ -29,8 +29,8 @@ msgstr "Pokaż inspektor"
msgid "The curve can't be read from this path, edit it as a single path" msgid "The curve can't be read from this path, edit it as a single path"
msgstr "Z tego odcinka nie da się odczytać łuku, edytuj go jako pojedynczy odcinek" msgstr "Z tego odcinka nie da się odczytać łuku, edytuj go jako pojedynczy odcinek"
msgid "Laying track: LMB adds the points" msgid "Laying track: each click lays a piece, Esc ends"
msgstr "Układanie toru: LPM dodaje punkty" msgstr "Układanie toru: każdy klik kładzie odcinek, Esc kończy"
msgid "Switch: drag to set its direction and side" msgid "Switch: drag to set its direction and side"
msgstr "Rozjazd: przeciągnij, aby ustawić kierunek i stronę" msgstr "Rozjazd: przeciągnij, aby ustawić kierunek i stronę"
@@ -2835,8 +2835,8 @@ msgstr "Klik: dołącz do tego końca i ułóż"
msgid "Click: start of the new track" msgid "Click: start of the new track"
msgstr "Klik: początek nowego toru" msgstr "Klik: początek nowego toru"
msgid "Click: next point" msgid "Click: lay the track to here"
msgstr "Klik: następny punkt" msgstr "Klik: połóż tor do tego miejsca"
msgid "Press: where the model starts, drag to its end" msgid "Press: where the model starts, drag to its end"
msgstr "Wciśnij: początek modelu, przeciągnij do jego końca" msgstr "Wciśnij: początek modelu, przeciągnij do jego końca"
@@ -2901,8 +2901,8 @@ msgstr "Klik: edytuj ten rozjazd"
msgid "Click: choose this track, then the type of the switch" msgid "Click: choose this track, then the type of the switch"
msgstr "Klik: wybierz ten tor, potem typ rozjazdu" msgstr "Klik: wybierz ten tor, potem typ rozjazdu"
msgid "Click: put a switch into this track" msgid "Click: choose this track, then press and drag on it for the switch"
msgstr "Klik: wstaw rozjazd w ten tor" msgstr "Klik: wybierz ten tor, potem wciśnij i przeciągnij na nim, by wstawić rozjazd"
msgid "Click: the grade line through this track" msgid "Click: the grade line through this track"
msgstr "Klik: niweleta przez ten tor" msgstr "Klik: niweleta przez ten tor"
@@ -4948,8 +4948,8 @@ msgstr "Skróty klawiszowe"
msgid "Lay track / switch / turntable" msgid "Lay track / switch / turntable"
msgstr "Układanie toru / rozjazd / obrotnica" msgstr "Układanie toru / rozjazd / obrotnica"
msgid "Laying track: finish / take the last point back" msgid "Laying track: lay up to the cursor / take the last piece back"
msgstr "Układanie toru: zakończ / cofnij ostatni punkt" msgstr "Układanie toru: połóż do kursora / cofnij ostatni odcinek"
msgid "Leave the editor" msgid "Leave the editor"
msgstr "Wyjście z edytora" msgstr "Wyjście z edytora"
@@ -5487,3 +5487,75 @@ msgstr ", ostrzej niż rozjazd (R %.0f m)"
msgid ", the switch moved %.2f m" msgid ", the switch moved %.2f m"
msgstr ", rozjazd przesunięty o %.2f m" msgstr ", rozjazd przesunięty o %.2f m"
msgid "take back the last piece"
msgstr "cofnij ostatni odcinek"
msgid "The last piece was taken back"
msgstr "Ostatni odcinek cofnięty"
msgid "R %.0f m doesn't reach the cursor, the arc goes through it with R %.0f m"
msgstr "R %.0f m nie sięga kursora, łuk przechodzi przez niego z R %.0f m"
msgid "First steps"
msgstr "Pierwsze kroki"
msgid "Laying the first track, step by step"
msgstr "Pierwszy tor krok po kroku"
msgid "The ticks fill in by themselves, the window doesn't stop anything"
msgstr "Kroki odhaczają się same, okno niczego nie blokuje"
msgid "F3: the Tracks field of work"
msgstr "F3: dział Tory"
msgid "L: lay track"
msgstr "L: układanie toru"
msgid "Click on the ground, then once more: a straight. Each next click lays a piece and goes on from its end, Esc ends"
msgstr "Kliknij na terenie, potem jeszcze raz: prosta. Każdy kolejny klik kładzie odcinek i ciągnie tor dalej z jego końca, Esc kończy"
msgid "While laying on: Ctrl+wheel changes the radius, Shift+wheel the shape (arc then straight, straight then arc, arc, straight). The plain wheel zooms"
msgstr "W trakcie układania: Ctrl+kółko zmienia promień, Shift+kółko kształt (łuk i prosta, prosta i łuk, łuk, prosta). Samo kółko przybliża widok"
msgid "Joining: press on a free end (magenta ring) and drag onto another free end"
msgstr "Łączenie: wciśnij na wolnym końcu (różowy pierścień) i przeciągnij na inny wolny koniec"
msgid "T: switch. Click a straight, then press on it and drag: along sets the direction, sideways the side"
msgstr "T: rozjazd. Kliknij prostą, potem wciśnij na niej i przeciągnij: wzdłuż ustala kierunek, w bok stronę"
msgid "Ctrl+Z takes a step back, Backspace while laying takes back the last piece"
msgstr "Ctrl+Z cofa krok, Backspace w trakcie układania cofa ostatni odcinek"
msgid "Ctrl+S saves the scenery"
msgstr "Ctrl+S zapisuje scenerię"
msgid "RMB held + W S A D: fly the camera, F: fly to the selected, F1: all the keys"
msgstr "Wciśnięty PPM + W S A D: lot kamerą, F: leć do zaznaczonego, F1: wszystkie klawisze"
msgid "No ground under the cursor: track goes onto the level 0 plane, drawn as a grid. Terrain: F5"
msgstr "Pod kursorem nie ma terenu: tor trafia na płaszczyznę na wysokości 0, widoczną jako siatka. Teren: F5"
msgid "Show when the editor opens"
msgstr "Pokazuj po otwarciu edytora"
msgid "L, LMB, LMB..."
msgstr "L, LPM, LPM..."
msgid "Lay track: each click lays a piece and goes on from its end"
msgstr "Układanie toru: każdy klik kładzie odcinek i ciągnie tor dalej z jego końca"
msgid "Lead a track out of it; onto another free end: join the two"
msgstr "Wyprowadza z niego tor; na inny wolny koniec: łączy oba"
msgid "Laying: radius"
msgstr "Układanie: promień"
msgid "Laying: arc then straight, straight then arc, arc, straight; at a free end: another way to join"
msgstr "Układanie: łuk i prosta, prosta i łuk, łuk, prosta; na wolnym końcu: inny sposób połączenia"
msgid "0-9 while laying"
msgstr "0-9 w trakcie układania"
msgid "Type the length (or the radius) of the next piece, Enter lays it"
msgstr "Wpisz długość (lub promień) następnego odcinka, Enter go kładzie"