17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 09:17:28 +01:00

feat(editor): the top view zooms out to 50 km for a whole line, from 0.5 m per pixel the rail tracks are drawn as thin lines and the hover reaches 16 px around them, only the tracks near the cursor are checked; handles drag beyond 200 m from the camera in Tracks; Plan works with the mouse alone and without the gizmo: the grips of a straight, the vertices and the radius of a curve drag directly, the break tool is press on the straight and drag, the panel tool stays chosen afterwards, the Q/W/E bar is hidden in Plan; self-test command ortho

This commit is contained in:
maj00r
2026-10-12 02:49:07 +02:00
parent 16c90eb674
commit f81698eaf1
7 changed files with 120 additions and 111 deletions

View File

@@ -1407,6 +1407,8 @@ bool editor_mode::update()
render_viewport_overlays();
render_track_inspector();
if (ui()->mode() == nodebank_panel::TRACK)
draw_track_lines();
if (selected_track())
draw_track_overlay();
if (route_active())
@@ -4780,7 +4782,7 @@ void editor_mode::render_gizmo_options()
{
ImGui::SameLine();
ImGui::SetNextItemWidth(120.0f);
ImGui::SliderFloat(STR_C("Extent (m), wheel / MMB pan"), &Global.EditorOrthoExtent, 5.0f, 5000.0f, "%.0f", ImGuiSliderFlags_Logarithmic);
ImGui::SliderFloat(STR_C("Extent (m), wheel / MMB pan"), &Global.EditorOrthoExtent, 5.0f, 50000.0f, "%.0f", ImGuiSliderFlags_Logarithmic);
if (ImGui::IsItemHovered())
ImGui::SetTooltip("%s", STR_C("Wheel zooms. Hold the wheel and drag to pan."));
}
@@ -4825,6 +4827,18 @@ void editor_mode::render_gizmo_options()
void editor_mode::render_gizmo()
{
if (straights_active())
{
render_straight_gizmo();
return;
}
if (route_active())
{
render_route_gizmo();
return;
}
// the transform gizmo is suppressed while editing terrain, so the brush/chunk tool owns the mouse
if (!m_gizmo_enabled || terrain_brush() || terrain_clicking())
{
@@ -4838,18 +4852,6 @@ void editor_mode::render_gizmo()
return;
}
if (straights_active())
{
render_straight_gizmo();
return;
}
if (route_active())
{
render_route_gizmo();
return;
}
if (!m_node && m_instance == 0)
{
m_gizmo_using = false;
@@ -5123,7 +5125,7 @@ void editor_mode::on_scroll(double const Xoffset, double const Yoffset)
if (false == Global.EditorOrtho || ImGui::GetIO().WantCaptureMouse)
return;
auto const before{Global.EditorOrthoExtent};
auto const after{std::clamp(before * static_cast<float>(std::pow(0.85, Yoffset)), 5.0f, 5000.0f)};
auto const after{std::clamp(before * static_cast<float>(std::pow(0.85, Yoffset)), 5.0f, 50000.0f)};
screen_projection const projection;
glm::dvec3 origin;
glm::dvec3 direction;
@@ -5288,7 +5290,7 @@ void editor_mode::exit()
m_track_snap = {};
m_track_point = {};
m_route = {};
m_route_gizmo_using = false;
m_route_dragging = false;
if (Global.EditorOrtho)
toggle_ortho();

View File

@@ -876,7 +876,6 @@ class editor_mode : public application_mode, private editor_track::observer
std::vector<std::pair<TTrack *, editor_track::state>> drag_states;
glm::dvec3 preview_start{0.0};
glm::dvec3 preview_end{0.0};
glm::mat4 gizmo{1.0f};
glm::dvec3 edit_start{0.0};
glm::dvec3 edit_end{0.0};
double edit_length{0.0};
@@ -886,17 +885,16 @@ class editor_mode : public application_mode, private editor_track::observer
bool auto_transitions{true};
double transition{0.0};
int tool{0};
int tool_before{0};
bool tool_mouse{false};
std::vector<glm::dvec3> detour;
editor_track::straight detour_line;
glm::dvec3 detour_mouse{0.0};
bool tool_placed{false};
bool tool_dragging{false};
double tool_at{0.0};
glm::dvec3 tool_point{0.0};
glm::dvec3 tool_handle{0.0};
editor_track::straight tool_line;
glm::mat4 tool_gizmo{1.0f};
std::string status;
};
void render_straights_ui();
@@ -908,7 +906,6 @@ class editor_mode : public application_mode, private editor_track::observer
void straights_apply(std::vector<editor_track::straight> const &Lines, std::vector<std::pair<glm::dvec3, glm::dvec3>> const &Ends);
bool in_straight_set(editor_track::straight const &Line) const;
bool place_straight_tool();
void render_straight_tool_gizmo();
void apply_straight_tool();
double straight_tool_radius(editor_track::straight const &Line) const;
void straight_reshape(editor_track::straight const &Line, double const From, double const To, std::function<glm::dvec3(glm::dvec3 const &)> const &Tail, double const Radius);
@@ -1264,6 +1261,7 @@ class editor_mode : public application_mode, private editor_track::observer
track_hover track_under_cursor(TTrack const *Only = nullptr, float const Pixels = 0.0f) const;
track_hover split_spot() const;
void update_track_hover();
void draw_track_lines() const;
void draw_track_hover() const;
struct track_intent
{
@@ -2114,8 +2112,7 @@ class editor_mode : public application_mode, private editor_track::observer
void render_infra_bound();
void draw_infra_overlay() const;
bool m_route_gizmo_using{false};
glm::mat4 m_route_gizmo{1.0f};
bool m_route_dragging{false};
float m_track_snap_radius{1.0f};
bool m_track_align_tangent{true};
bool m_track_drag_connected{true};

View File

@@ -149,6 +149,18 @@ editor_mode::selftest_result editor_mode::selftest_scene(std::string const &comm
save();
return selftest_result::handled;
}
if (command == "ortho")
{
float extent{0.0f};
words >> extent;
choose_work_area(work_area::tracks);
if (false == Global.EditorOrtho)
toggle_ortho();
if (extent > 0.0f)
Global.EditorOrthoExtent = std::clamp(extent, 5.0f, 50000.0f);
WriteLog(format("SELFTEST ortho extent %.0f", Global.EditorOrthoExtent));
return selftest_result::handled;
}
if (command == "mapstate")
{
auto const marked{scene::Layers.empty() ? std::vector<std::string>{} : scene::Layers.marked(scene::layer_handle{1}, "//$o")};

View File

@@ -665,7 +665,7 @@ std::string editor_mode::track_mode_name() const
if (false == m_straights.detour.empty())
return STR_C("Plan, straight: shift around, Shift+click the points");
if (m_straights.tool == 1)
return STR_C("Plan, straight: break, LMB on the straight");
return STR_C("Plan, straight: break, press on the straight and drag");
if (m_straights.tool == 2)
return STR_C("Plan, straight: S-curve, LMB on the straight");
return STR_C("Plan, straight: drag its ends or the middle");
@@ -2292,42 +2292,24 @@ void editor_mode::render_route_gizmo()
auto &route{m_route};
auto const count{static_cast<int>(route.design.vertices.size())};
bool const grip{route.grip >= 0 && route.grip < count && route.grip < static_cast<int>(route.result.vertex_chainages.size())};
if ((false == grip && (route.vertex < 0 || route.vertex >= count)) || false == m_gizmo_enabled)
if (false == grip && (route.vertex < 0 || route.vertex >= count))
{
m_route_gizmo_using = false;
m_route_dragging = false;
return;
}
gizmo_frame const frame;
auto const position{grip ? alignment::evaluate(route.result, route.result.vertex_chainages[route.grip]).position : route_vertex_position(route.vertex)};
glm::dvec3 moved{position};
bool held{false};
if (m_handle_drag.active)
if (false == handle_drag_step(moved))
{
held = handle_drag_step(moved);
if (false == held && false == m_route_gizmo_using)
return;
}
else
{
if (false == m_route_gizmo_using)
m_route_gizmo = glm::translate(glm::mat4(1.0f), glm::vec3(position - frame.camera));
frame.manipulate(ImGuizmo::TRANSLATE, m_route_gizmo, m_gizmo_snap);
held = ImGuizmo::IsUsing();
moved = frame.camera + glm::dvec3(m_route_gizmo[3]);
}
if (false == held)
{
if (m_route_gizmo_using)
if (m_route_dragging)
{
m_route_gizmo_using = false;
m_route_dragging = false;
route_apply();
}
return;
}
m_route_gizmo_using = true;
m_route_dragging = true;
if (grip)
{
auto &vertex{route.design.vertices[route.grip]};
@@ -3079,38 +3061,18 @@ void editor_mode::render_straight_gizmo()
typed_turn();
return;
}
if (state.tool_placed && m_gizmo_enabled)
{
render_straight_tool_gizmo();
return;
}
auto const &line{state.dragging ? state.drag_line : current_straight()};
if (state.handle < 0 || line.tracks.empty() || false == m_gizmo_enabled)
if (state.handle < 0 || line.tracks.empty())
{
state.dragging = false;
return;
}
gizmo_frame const frame;
glm::dvec3 const anchors[] = {line.start, line.end, (line.start + line.end) * 0.5};
auto const anchor{anchors[state.handle]};
glm::dvec3 handled{anchor};
bool held{false};
if (m_handle_drag.active)
{
held = handle_drag_step(handled);
if (false == held && false == state.dragging)
return;
}
else
{
if (false == state.dragging)
state.gizmo = glm::translate(glm::mat4(1.0f), glm::vec3(anchor - frame.camera));
frame.manipulate(ImGuizmo::TRANSLATE, state.gizmo, m_gizmo_snap);
held = ImGuizmo::IsUsing();
handled = frame.camera + glm::dvec3(state.gizmo[3]);
}
glm::dvec3 handled{anchors[state.handle]};
bool const held{handle_drag_step(handled)};
if (false == held && false == state.dragging)
return;
if (held)
{
@@ -3523,32 +3485,10 @@ bool editor_mode::place_straight_tool()
auto const reach{std::min(50.0, std::max(1.0, line.length - along))};
state.tool_handle = state.tool_point + axis * reach;
state.tool_placed = true;
state.tool_dragging = false;
state.handle = -1;
return true;
}
void editor_mode::render_straight_tool_gizmo()
{
auto &state{m_straights};
gizmo_frame const frame;
if (false == state.tool_dragging)
state.tool_gizmo = glm::translate(glm::mat4(1.0f), glm::vec3(state.tool_handle - frame.camera));
frame.manipulate(ImGuizmo::TRANSLATE, state.tool_gizmo, 0.0f);
if (ImGuizmo::IsUsing())
{
state.tool_dragging = true;
auto const moved{frame.camera + glm::dvec3(state.tool_gizmo[3])};
state.tool_handle = {moved.x, state.tool_handle.y, moved.z};
typed_turn();
}
else if (state.tool_dragging)
{
state.tool_dragging = false;
apply_straight_tool();
}
}
void editor_mode::apply_straight_tool()
{
auto &state{m_straights};
@@ -3596,11 +3536,11 @@ void editor_mode::apply_straight_tool()
void editor_mode::start_straight_gesture(int const Tool)
{
auto &state{m_straights};
auto const previous{state.tool};
state.tool_before = state.tool;
state.tool = Tool;
if (false == place_straight_tool())
{
state.tool = previous;
state.tool = state.tool_before;
return;
}
state.tool_mouse = true;
@@ -3611,7 +3551,7 @@ void editor_mode::finish_straight_gesture()
auto &state{m_straights};
state.tool_mouse = false;
apply_straight_tool();
state.tool = 0;
state.tool = state.tool_before;
state.tool_placed = false;
}
@@ -3879,7 +3819,7 @@ std::string editor_mode::track_readout() const
return text;
}
auto const &state{m_straights};
if (state.tool_placed && state.tool_dragging)
if (state.tool_placed && state.tool_mouse)
{
auto const &line{state.tool_line};
auto const offset{plan_of(state.tool_handle - state.tool_point)};
@@ -4304,7 +4244,7 @@ bool editor_mode::handle_drag_step(glm::dvec3 &Position)
drag.moved = true;
screen_projection const projection;
glm::dvec3 level;
if (projection.on_level(io.MousePos, drag.height, level) && glm::distance(level, glm::dvec3{Global.pCamera.Pos}) <= static_cast<double>(kMaxPlacementDistance))
if (projection.on_level(io.MousePos, drag.height, level) && (Global.editor_tracks || glm::distance(level, glm::dvec3{Global.pCamera.Pos}) <= static_cast<double>(kMaxPlacementDistance)))
drag.position = level + drag.offset;
Position = drag.position;
return true;
@@ -4377,7 +4317,7 @@ void editor_mode::point_drag_update()
}
screen_projection const projection;
glm::dvec3 position;
if (false == projection.on_level(io.MousePos, drag.height, position) || glm::distance(position, glm::dvec3{Global.pCamera.Pos}) > static_cast<double>(kMaxPlacementDistance))
if (false == projection.on_level(io.MousePos, drag.height, position) || (false == Global.editor_tracks && glm::distance(position, glm::dvec3{Global.pCamera.Pos}) > static_cast<double>(kMaxPlacementDistance)))
return;
m_track_snap = {};
if (editor_track::is_end(m_track_point.kind) && m_track_drag_points.empty())

View File

@@ -38,8 +38,11 @@ namespace
{
using geometry::bezier;
using geometry::plan_of;
float const kHoverPixels{12.0f};
float const kFarHoverPixels{16.0f};
double const kLinesFrom{0.5};
double const kHoverLabelDelay{0.35}; // s the cursor rests on a path before its data shows up
double const kFocusDistance{25.0};
auto const kContextClick{std::chrono::milliseconds(350)};
@@ -81,6 +84,19 @@ char const *type_of(TTrack const &Track)
}
}
double ortho_metres_per_pixel()
{
auto const height{ImGui::GetIO().DisplaySize.y};
return Global.EditorOrtho && height > 0.0f ? 2.0 * static_cast<double>(Global.EditorOrthoExtent) / static_cast<double>(height) : 0.0;
}
bool plan_near(bezier const &Curve, glm::dvec2 const &Point, double const Reach)
{
auto const centre{(plan_of(Curve.p0) + plan_of(Curve.p1) + plan_of(Curve.p2) + plan_of(Curve.p3)) * 0.25};
auto const radius{std::max({glm::distance(centre, plan_of(Curve.p0)), glm::distance(centre, plan_of(Curve.p1)), glm::distance(centre, plan_of(Curve.p2)), glm::distance(centre, plan_of(Curve.p3))})};
return glm::distance(centre, Point) <= radius + Reach;
}
double segment_distance2(glm::vec2 const &Point, glm::vec2 const &A, glm::vec2 const &B, double &T)
{
auto const ab{B - A};
@@ -98,7 +114,14 @@ editor_mode::track_hover editor_mode::track_under_cursor(TTrack const *Only, flo
auto const &io{ImGui::GetIO()};
screen_projection const projection;
glm::vec2 const mouse{io.MousePos.x, io.MousePos.y};
double best{Pixels > 0.0f ? Pixels * Pixels : Only != nullptr ? std::numeric_limits<double>::max() : kHoverPixels * kHoverPixels};
auto const metres{ortho_metres_per_pixel()};
auto const hover{metres >= kLinesFrom ? kFarHoverPixels : kHoverPixels};
double best{Pixels > 0.0f ? Pixels * Pixels : Only != nullptr ? std::numeric_limits<double>::max() : hover * hover};
glm::dvec3 origin;
glm::dvec3 direction;
projection.ray(io.MousePos, origin, direction);
bool const cull{metres > 0.0 && Only == nullptr};
auto const reach{cull ? std::sqrt(best) * metres : 0.0};
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || false == rail(*track) || track->m_editorremoved || (Only != nullptr && track != Only))
@@ -106,6 +129,8 @@ editor_mode::track_hover editor_mode::track_under_cursor(TTrack const *Only, flo
for (int i = 0; i < static_cast<int>(track->m_paths.size()); ++i)
{
bezier const curve{track->m_paths[i]};
if (cull && false == plan_near(curve, plan_of(origin), reach))
continue;
auto const count{std::clamp(static_cast<int>(curve.plan_length() / 3.0), 6, 64)};
auto previous{curve.point(0.0)};
ImVec2 from;
@@ -140,7 +165,7 @@ void editor_mode::update_track_hover()
{
if (typed_meaning() == nullptr)
m_typed.clear();
bool const blocked{ImGui::GetIO().WantCaptureMouse || mouseHold || m_track_set_using || m_track_box.active || m_input.mouse.button(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS || m_track_gizmo_using || m_route_gizmo_using || m_extend.active || m_switch.placing ||
bool const blocked{ImGui::GetIO().WantCaptureMouse || mouseHold || m_track_set_using || m_track_box.active || m_input.mouse.button(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS || m_track_gizmo_using || m_route_dragging || m_extend.active || m_switch.placing ||
m_straights.dragging || m_lay.active};
if (blocked)
{
@@ -152,6 +177,37 @@ void editor_mode::update_track_hover()
m_hover = found;
}
void editor_mode::draw_track_lines() const
{
auto const metres{ortho_metres_per_pixel()};
if (metres < kLinesFrom)
return;
auto const &io{ImGui::GetIO()};
screen_projection const projection;
glm::dvec2 const centre{Global.pCamera.Pos.x, Global.pCamera.Pos.z};
auto const reach{0.5 * metres * std::hypot(static_cast<double>(io.DisplaySize.x), static_cast<double>(io.DisplaySize.y))};
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || false == rail(*track))
continue;
for (auto const &path : track->m_paths)
{
bezier const curve{path};
if (false == plan_near(curve, centre, reach))
continue;
auto const count{std::clamp(static_cast<int>(curve.plan_length(4) / metres / 8.0), 1, 32)};
auto previous{curve.point(0.0)};
for (int k = 1; k <= count; ++k)
{
auto const next{curve.point(static_cast<double>(k) / count)};
projection.line(drawlist, previous, next, IM_COL32(235, 235, 235, 150), 1.5f);
previous = next;
}
}
}
}
void editor_mode::draw_track_hover() const
{
using kind = track_intent::kind;
@@ -180,7 +236,7 @@ void editor_mode::draw_track_hover() const
bool editor_mode::track_gesture_on() const
{
return mouseHold || m_track_set_using || m_track_box.active || m_input.mouse.button(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS || m_track_gizmo_using || m_route_gizmo_using || m_extend.active || m_switch.placing || m_straights.dragging || m_straights.tool_mouse ||
return mouseHold || m_track_set_using || m_track_box.active || m_input.mouse.button(GLFW_MOUSE_BUTTON_RIGHT) == GLFW_PRESS || m_track_gizmo_using || m_route_dragging || m_extend.active || m_switch.placing || m_straights.dragging || m_straights.tool_mouse ||
m_point_drag.active || m_handle_drag.active || m_sweep.dragging || m_turntable.placing || m_signal.placing || m_signal.moving || m_switch.slide.active || ImGuizmo::IsUsing();
}
@@ -349,7 +405,7 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
if (m_straights.tool != 0 && false == line.tracks.empty())
{
intent.position = line.start + glm::dvec3{line.direction.x, line.grade, line.direction.y} * std::clamp(line.along(ground), 0.0, line.length);
return make(kind::straight_tool, m_straights.tool == 1 ? "Click: break the straight here" : "Click: start of the shift");
return make(kind::straight_tool, m_straights.tool == 1 ? "Press: break the straight here, drag to turn the rest" : "Click: start of the shift");
}
}
if ((m_track_tab == track_tab::turnout || m_track_tab == track_tab::path) && m_hover.track != nullptr && switch_slides(*m_hover.track))
@@ -445,7 +501,12 @@ void editor_mode::track_press(track_intent const &Intent)
}
case kind::straight_break: start_straight_gesture(1); return;
case kind::detour: add_detour_point(); return;
case kind::straight_tool: place_straight_tool(); return;
case kind::straight_tool:
if (m_straights.tool == 1)
start_straight_gesture(1);
else
place_straight_tool();
return;
case kind::straight_handle:
m_straights.handle = Intent.index;
handle_drag_start(Intent.position);
@@ -758,7 +819,7 @@ char const *editor_mode::typed_meaning() const
auto const &straights{m_straights};
if (straights.dragging)
return straights.handle == 2 ? "type the shift, + to the right" : "type the length";
if (straights.tool == 1 && (straights.tool_mouse || straights.tool_dragging))
if (straights.tool == 1 && straights.tool_mouse)
return "type the turn in degrees, + to the right";
if (m_lay.active && false == m_lay.points.empty())
return "type the length of the next leg, Enter adds the point";

View File

@@ -399,7 +399,7 @@ void editor_mode::render_toolbar_tools()
void editor_mode::render_viewport_gizmo()
{
// the gizmo moves the models and the tracks; on the terrain the mouse is the brush's
if (false == m_gizmo_enabled || current_work_area() == work_area::terrain)
if (false == m_gizmo_enabled || current_work_area() == work_area::terrain || (ui()->mode() == nodebank_panel::TRACK && track_plan_tab()))
return;
auto const tooltip{STR_C("What the handles of the gizmo on the selection do when dragged; Ctrl held snaps the values")};
std::array<tool_button, 4> const buttons{{