mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-02 23:16:22 +01:00
Merge remote-tracking branch 'origin/feat/switch-blades' into feat/renderers-cleanup
This commit is contained in:
@@ -12,12 +12,16 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
#include "application/editormodeshared.h"
|
||||
#include "application/editormodetrackshared.h"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "editor/editorSettings.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
|
||||
#include "utilities/Globals.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Track.h"
|
||||
#include "utilities/utilities.h"
|
||||
#include "scene/scene.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "simulation/simulation.h"
|
||||
@@ -29,11 +33,13 @@ http://mozilla.org/MPL/2.0/.
|
||||
#include <unordered_set>
|
||||
#include <unordered_map>
|
||||
#include <queue>
|
||||
#include <deque>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace
|
||||
{
|
||||
@@ -62,17 +68,6 @@ bool rail(TTrack const &Track)
|
||||
return (Track.iCategoryFlag & 15) == kRailCategory && false == Track.m_editorremoved && Track.visible();
|
||||
}
|
||||
|
||||
std::string name_of(TTrack const &Track)
|
||||
{
|
||||
return Track.name().empty() ? std::string{STR_C("(no name)")} : Track.name();
|
||||
}
|
||||
|
||||
std::string lower(std::string Text)
|
||||
{
|
||||
std::transform(Text.begin(), Text.end(), Text.begin(), [](unsigned char const Character) { return static_cast<char>(std::tolower(Character)); });
|
||||
return Text;
|
||||
}
|
||||
|
||||
char const *type_of(TTrack const &Track)
|
||||
{
|
||||
switch (Track.eType)
|
||||
@@ -92,6 +87,32 @@ double ortho_metres_per_pixel()
|
||||
return Global.EditorOrtho && height > 0.0f ? 2.0 * static_cast<double>(Global.EditorOrthoExtent) / static_cast<double>(height) : 0.0;
|
||||
}
|
||||
|
||||
// the bands of the colouring of the tracks: up to the value, the colour and its label in the legend
|
||||
struct colour_band
|
||||
{
|
||||
double below;
|
||||
ImU32 colour;
|
||||
char const *label;
|
||||
};
|
||||
double constexpr kBeyond{std::numeric_limits<double>::max()};
|
||||
ImU32 const kUncoloured{IM_COL32(200, 200, 200, 170)};
|
||||
double const kColouredReach{1500.0}; // m around the camera in the 3D view
|
||||
double const kLabelsTo{1.0}; // m a pixel of the top view, farther out the labels of the tracks are left out
|
||||
std::vector<colour_band> const kRadiusBands{{300.0, IM_COL32(235, 60, 60, 235), "R < 300 m"}, {600.0, IM_COL32(245, 140, 50, 235), "R 300-600 m"}, {1200.0, IM_COL32(240, 220, 60, 235), "R 600-1200 m"},
|
||||
{3000.0, IM_COL32(110, 210, 80, 235), "R 1200-3000 m"}, {kBeyond, IM_COL32(70, 190, 235, 235), "R > 3000 m"}};
|
||||
std::vector<colour_band> const kSpeedBands{{40.5, IM_COL32(235, 60, 60, 235), "<= 40 km/h"}, {60.5, IM_COL32(245, 140, 50, 235), "50-60 km/h"}, {80.5, IM_COL32(240, 220, 60, 235), "70-80 km/h"},
|
||||
{100.5, IM_COL32(110, 210, 80, 235), "90-100 km/h"}, {120.5, IM_COL32(70, 190, 235, 235), "110-120 km/h"}, {kBeyond, IM_COL32(170, 120, 255, 235), "> 120 km/h"}};
|
||||
std::vector<colour_band> const kGradeBands{{2.0, kUncoloured, "0-2 \xe2\x80\xb0"}, {5.0, IM_COL32(110, 210, 80, 235), "2-5 \xe2\x80\xb0"}, {10.0, IM_COL32(240, 220, 60, 235), "5-10 \xe2\x80\xb0"},
|
||||
{20.0, IM_COL32(245, 140, 50, 235), "10-20 \xe2\x80\xb0"}, {kBeyond, IM_COL32(235, 60, 60, 235), "> 20 \xe2\x80\xb0"}};
|
||||
|
||||
ImU32 band_colour(std::vector<colour_band> const &Bands, double const Value)
|
||||
{
|
||||
for (auto const &band : Bands)
|
||||
if (Value < band.below)
|
||||
return band.colour;
|
||||
return kUncoloured;
|
||||
}
|
||||
|
||||
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};
|
||||
@@ -184,31 +205,358 @@ void editor_mode::update_track_hover()
|
||||
void editor_mode::draw_track_lines() const
|
||||
{
|
||||
auto const metres{ortho_metres_per_pixel()};
|
||||
if (metres < kLinesFrom)
|
||||
auto const colouring{ui()->mode() == nodebank_panel::TRACK ? m_track_colouring : track_colouring::plain};
|
||||
// the top view of the rails alone draws them in a colour of their own, as on a plan: both rails where they're apart on the screen
|
||||
auto const rails{metres > 0.0 && Global.EditorOrthoTracks > 1 && colouring == track_colouring::plain};
|
||||
if (metres < kLinesFrom && colouring == track_colouring::plain && false == rails)
|
||||
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{Global.EditorOrtho ? 0.5 * metres * std::hypot(static_cast<double>(io.DisplaySize.x), static_cast<double>(io.DisplaySize.y)) : kColouredReach};
|
||||
auto const width{rails ? std::max(2.0f, static_cast<float>(0.07 / metres)) : colouring == track_colouring::plain ? 1.5f : 3.0f};
|
||||
auto const railcolour{EditorSettings.top_rails()};
|
||||
std::vector<glm::dvec3> points;
|
||||
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || false == rail(*track))
|
||||
continue;
|
||||
auto const velocity{editor_track::velocity(*track)};
|
||||
for (auto const &path : track->m_paths)
|
||||
{
|
||||
bezier const curve{path};
|
||||
if (false == plan_near(curve, centre, reach))
|
||||
continue;
|
||||
auto colour{rails ? railcolour : IM_COL32(235, 235, 235, 150)};
|
||||
if (colouring == track_colouring::radius)
|
||||
{
|
||||
auto const radius{::track_mode::piece_plan_radius(path)};
|
||||
colour = radius >= 0.5 ? band_colour(kRadiusBands, radius) : kUncoloured;
|
||||
}
|
||||
else if (colouring == track_colouring::speed)
|
||||
colour = velocity > 0.0 ? band_colour(kSpeedBands, velocity) : kUncoloured;
|
||||
auto const length{curve.plan_length(4)};
|
||||
auto const count{std::clamp(static_cast<int>(length / std::max(metres, 0.5) / 8.0), colouring == track_colouring::grade ? std::clamp(static_cast<int>(length / 10.0), 1, 8) : 1, rails ? 128 : 32)};
|
||||
if (rails && track->fTrackWidth / metres > 6.0)
|
||||
{
|
||||
points.clear();
|
||||
for (int k = 0; k <= count; ++k)
|
||||
points.push_back(curve.point(static_cast<double>(k) / count));
|
||||
auto const half{(track->fTrackWidth + 0.07) * 0.5};
|
||||
auto const across = [&](std::size_t const K) {
|
||||
auto const along{points[std::min(K + 1, points.size() - 1)] - points[K > 0 ? K - 1 : 0]};
|
||||
auto const normal{glm::dvec3{-along.z, 0.0, along.x}};
|
||||
auto const size{glm::length(normal)};
|
||||
return size > 1e-9 ? normal * (half / size) : glm::dvec3{0.0};
|
||||
};
|
||||
for (auto const side : {-1.0, 1.0})
|
||||
for (std::size_t k = 1; k < points.size(); ++k)
|
||||
projection.line(drawlist, points[k - 1] + across(k - 1) * side, points[k] + across(k) * side, colour, width);
|
||||
continue;
|
||||
}
|
||||
auto previous{curve.point(0.0)};
|
||||
for (int k = 1; k <= count; ++k)
|
||||
{
|
||||
auto const next{curve.point(static_cast<double>(k) / count)};
|
||||
if (colouring == track_colouring::grade)
|
||||
{
|
||||
auto const run{geometry::plan_distance(previous, next)};
|
||||
colour = run > 1e-3 ? band_colour(kGradeBands, std::abs(next.y - previous.y) / run * 1000.0) : kUncoloured;
|
||||
}
|
||||
projection.line(drawlist, previous, next, colour, width);
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::draw_track_labels() const
|
||||
{
|
||||
auto const metres{ortho_metres_per_pixel()};
|
||||
if (false == m_track_labels || metres <= 0.0 || metres > kLabelsTo)
|
||||
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())};
|
||||
auto const low{editor_ui::view_min()};
|
||||
auto const high{editor_ui::view_max()};
|
||||
struct track_label
|
||||
{
|
||||
int rank;
|
||||
double order;
|
||||
glm::dvec3 point;
|
||||
std::string text;
|
||||
ImU32 colour;
|
||||
};
|
||||
std::vector<track_label> labels;
|
||||
// the screen in cells, the ones the tracks are drawn over marked: a label keeps off them when it can
|
||||
float constexpr cell{8.0f};
|
||||
auto const columns{std::max(1, static_cast<int>((high.x - low.x) / cell) + 1)};
|
||||
auto const rows{std::max(1, static_cast<int>((high.y - low.y) / cell) + 1)};
|
||||
std::vector<char> covered(static_cast<std::size_t>(columns) * rows, 0);
|
||||
auto const cell_of = [&](float const X, float const Y, int &Column, int &Row) {
|
||||
Column = static_cast<int>((X - low.x) / cell);
|
||||
Row = static_cast<int>((Y - low.y) / cell);
|
||||
return Column >= 0 && Row >= 0 && Column < columns && Row < rows;
|
||||
};
|
||||
// one label for a curve, from its first transition curve to its last, and one for a row of straights in line, not one for each
|
||||
// of their paths: the paths of a curve are pieces of it, a piece of a transition curve has no radius of its own to name
|
||||
std::unordered_set<TTrack const *> labelled;
|
||||
auto const plain = [&](TTrack const *Other) { return Other != nullptr && Other->eType == tt_Normal && rail(*Other) && false == Other->m_paths.empty(); };
|
||||
auto const heading = [](TTrack const &Other) { return plan_of(Other.m_paths.front().points[segment_data::point::end] - Other.m_paths.front().points[segment_data::point::start]); };
|
||||
auto const own_turn = [](TTrack const &Other, bool const Forward) {
|
||||
bezier const curve{Other.m_paths.front()};
|
||||
auto const begin{plan_of(curve.first(0.0))};
|
||||
auto const finish{plan_of(curve.first(1.0))};
|
||||
if (glm::length(begin) < 1e-9 || glm::length(finish) < 1e-9)
|
||||
return 0.0;
|
||||
auto const turn{geometry::signed_angle(glm::normalize(begin), glm::normalize(finish))};
|
||||
return Forward ? turn : -turn;
|
||||
};
|
||||
// the paths joined in a row with the one given, both ways, as long as each next one belongs with it; each with the flag of being
|
||||
// gone along from its start
|
||||
auto const row = [&](TTrack &Track, std::function<bool(TTrack const &, bool)> const &Belongs) {
|
||||
std::deque<std::pair<TTrack *, bool>> run{{&Track, true}};
|
||||
auto const known = [&](TTrack const *Other) { return std::any_of(run.begin(), run.end(), [&](auto const &Member) { return Member.first == Other; }); };
|
||||
for (auto const throughend : {true, false})
|
||||
{
|
||||
TTrack *previous{&Track};
|
||||
TTrack *current{throughend ? Track.trNext : Track.trPrev};
|
||||
while (plain(current) && false == known(current) && false == labelled.contains(current))
|
||||
{
|
||||
auto const entersatstart{current->trPrev == previous};
|
||||
if (false == entersatstart && current->trNext != previous)
|
||||
break;
|
||||
if (false == Belongs(*current, throughend == entersatstart))
|
||||
break;
|
||||
if (throughend)
|
||||
run.emplace_back(current, entersatstart);
|
||||
else
|
||||
run.emplace_front(current, false == entersatstart);
|
||||
previous = current;
|
||||
current = entersatstart ? current->trNext : current->trPrev;
|
||||
}
|
||||
}
|
||||
return std::vector<std::pair<TTrack *, bool>>{run.begin(), run.end()};
|
||||
};
|
||||
// the curve as the plan reads it: the radius of the arc, of each arc of a compound one, the transition curves and the cant
|
||||
auto const curve_text = [](editor_track::curve const &Curve) {
|
||||
if (Curve.designed.has_value() && Curve.designed->spirals)
|
||||
return format("2xL %.0f R %.0f", Curve.transition_in, Curve.radius);
|
||||
std::string text{"R "};
|
||||
if (Curve.compound && Curve.arcs.size() >= 2)
|
||||
for (std::size_t i = 0; i < Curve.arcs.size(); ++i)
|
||||
text += format(i == 0 ? "%.0f" : "/%.0f", Curve.arcs[i].radius);
|
||||
else
|
||||
text += format("%.0f", Curve.radius);
|
||||
if (Curve.transition_in >= 0.5 || Curve.transition_out >= 0.5)
|
||||
text += std::abs(Curve.transition_in - Curve.transition_out) < 0.5 ? format(" L %.0f", Curve.transition_in) : format(" L %.0f/%.0f", Curve.transition_in, Curve.transition_out);
|
||||
// the arcs of a compound curve kept with its design have each a cant of their own: all shown where they aren't the same
|
||||
auto const cants{Curve.designed.has_value() && Curve.compound ? alignment::compound_cants(*Curve.designed) : std::vector<double>{}};
|
||||
if (std::any_of(cants.begin(), cants.end(), [&](double const Cant) { return std::abs(Cant - cants.front()) >= 0.5; }))
|
||||
for (std::size_t i = 0; i < cants.size(); ++i)
|
||||
text += format(i == 0 ? " h %.0f" : "/%.0f", cants[i]);
|
||||
else if (Curve.cant >= 0.5)
|
||||
text += format(" h %.0f", Curve.cant);
|
||||
return text;
|
||||
};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || false == rail(*track))
|
||||
if (track == nullptr || false == rail(*track) || track->m_paths.empty())
|
||||
continue;
|
||||
bezier const first{track->m_paths.front()};
|
||||
if (false == plan_near(first, centre, reach))
|
||||
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 count{std::clamp(static_cast<int>(curve.plan_length() / (metres * cell)), 2, 256)};
|
||||
ImVec2 previous;
|
||||
bool known{false};
|
||||
for (int k = 0; 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;
|
||||
ImVec2 at;
|
||||
if (false == projection.project(curve.point(static_cast<double>(k) / count), at))
|
||||
{
|
||||
known = false;
|
||||
continue;
|
||||
}
|
||||
if (known)
|
||||
{
|
||||
auto const steps{std::max(1, static_cast<int>(std::max(std::abs(at.x - previous.x), std::abs(at.y - previous.y)) / cell * 2.0f))};
|
||||
for (int step = 0; step <= steps; ++step)
|
||||
{
|
||||
auto const t{static_cast<float>(step) / steps};
|
||||
int column, row;
|
||||
if (cell_of(previous.x + (at.x - previous.x) * t, previous.y + (at.y - previous.y) * t, column, row))
|
||||
covered[static_cast<std::size_t>(row) * columns + column] = 1;
|
||||
}
|
||||
}
|
||||
previous = at;
|
||||
known = true;
|
||||
}
|
||||
}
|
||||
if (track->eType == tt_Switch)
|
||||
{
|
||||
auto text{name_of(*track)};
|
||||
if (auto const record{switch_store::of(*track)})
|
||||
text += " " + switch_store::label(*record);
|
||||
labels.push_back({0, 0.0, first.point(0.0), text, IM_COL32(255, 220, 120, 255)});
|
||||
continue;
|
||||
}
|
||||
if (labelled.contains(track) || false == plain(track))
|
||||
continue;
|
||||
auto const straight{editor_track::is_straight(*track, m_straights.tolerance)};
|
||||
std::vector<std::pair<TTrack *, bool>> run;
|
||||
if (straight)
|
||||
{
|
||||
// the straights in line: the next one parallel to this one, a bend between them is the end of the row
|
||||
auto const along{heading(*track)};
|
||||
run = row(*track, [&](TTrack const &Other, bool) {
|
||||
auto const other{heading(Other)};
|
||||
return editor_track::is_straight(Other, m_straights.tolerance) && glm::length(along) > 1e-6 && glm::length(other) > 1e-6 &&
|
||||
std::abs(geometry::cross(glm::normalize(along), glm::normalize(other))) < 0.002;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
// the pieces of a curve turning the same way: one turning the other way starts a curve of its own
|
||||
auto const side{own_turn(*track, true)};
|
||||
run = row(*track, [&](TTrack const &Other, bool const Forward) { return false == editor_track::is_straight(Other, m_straights.tolerance) && own_turn(Other, Forward) * side >= 0.0; });
|
||||
}
|
||||
double length{0.0};
|
||||
for (auto const &member : run)
|
||||
{
|
||||
labelled.insert(member.first);
|
||||
length += bezier{member.first->m_paths.front()}.plan_length();
|
||||
}
|
||||
if (length < 0.5)
|
||||
continue;
|
||||
// the label in the middle of the row
|
||||
glm::dvec3 middle{first.point(0.5)};
|
||||
auto left{length * 0.5};
|
||||
for (auto const &[member, forward] : run)
|
||||
{
|
||||
bezier const piece{member->m_paths.front()};
|
||||
auto const span{piece.plan_length()};
|
||||
if (left <= span || &member == &run.back().first)
|
||||
{
|
||||
auto const share{std::clamp(span > 1e-6 ? left / span : 0.5, 0.0, 1.0)};
|
||||
middle = piece.point(forward ? share : 1.0 - share);
|
||||
break;
|
||||
}
|
||||
left -= span;
|
||||
}
|
||||
if (straight)
|
||||
{
|
||||
labels.push_back({2, -length, middle, format(STR_C("straight %.0f m"), length), IM_COL32(200, 215, 230, 225)});
|
||||
continue;
|
||||
}
|
||||
// a track bent by hand, as an S between two points dragged, has no radius to read: its length alone
|
||||
editor_track::curve shape;
|
||||
if (editor_track::analyse_curve(run, m_route.design.norms.gauge, shape) && shape.radius >= 0.5 && shape.radius <= 50000.0)
|
||||
labels.push_back({1, shape.radius, middle, curve_text(shape), IM_COL32(235, 235, 235, 240)});
|
||||
else
|
||||
labels.push_back({2, -length, middle, format("L %.0f m", length), IM_COL32(235, 235, 235, 240)});
|
||||
}
|
||||
std::sort(labels.begin(), labels.end(), [](track_label const &A, track_label const &B) { return A.rank != B.rank ? A.rank < B.rank : A.order < B.order; });
|
||||
// each label where it reads best: beside its track if it's free there, else moved out to the nearest room, a line leading back
|
||||
std::vector<std::pair<ImVec2, ImVec2>> taken;
|
||||
auto const tracks_under = [&](ImVec2 const &From, ImVec2 const &To) {
|
||||
int count{0};
|
||||
for (auto y = From.y; y <= To.y + cell * 0.5f; y += cell)
|
||||
for (auto x = From.x; x <= To.x + cell * 0.5f; x += cell)
|
||||
{
|
||||
int column, row;
|
||||
if (cell_of(std::min(x, To.x), std::min(y, To.y), column, row))
|
||||
count += covered[static_cast<std::size_t>(row) * columns + column];
|
||||
}
|
||||
return count;
|
||||
};
|
||||
float constexpr kRings[]{0.0f, 26.0f, 42.0f, 62.0f, 86.0f, 116.0f, 150.0f};
|
||||
int constexpr kDirections{16};
|
||||
for (auto const &label : labels)
|
||||
{
|
||||
ImVec2 at;
|
||||
if (false == projection.project(label.point, at) || at.x < low.x || at.y < low.y || at.x > high.x || at.y > high.y)
|
||||
continue;
|
||||
auto const size{ImGui::CalcTextSize(label.text.c_str())};
|
||||
auto const width{size.x + 8.0f};
|
||||
auto const height{size.y + 4.0f};
|
||||
ImVec2 const nearest{at.x, at.y - 10.0f - height * 0.5f};
|
||||
auto best{std::numeric_limits<float>::max()};
|
||||
ImVec2 from, to;
|
||||
for (auto const ring : kRings)
|
||||
for (int k = 0; k < (ring > 0.0f ? kDirections : 1); ++k)
|
||||
{
|
||||
auto const angle{static_cast<float>(k) * 6.2831853f / kDirections};
|
||||
ImVec2 const middle{nearest.x + std::cos(angle) * ring * 1.6f, nearest.y + std::sin(angle) * ring};
|
||||
ImVec2 const a{middle.x - width * 0.5f, middle.y - height * 0.5f};
|
||||
ImVec2 const b{middle.x + width * 0.5f, middle.y + height * 0.5f};
|
||||
if (a.x < low.x || a.y < low.y || b.x > high.x || b.y > high.y)
|
||||
continue;
|
||||
if (std::any_of(taken.begin(), taken.end(), [&](auto const &Rect) { return a.x < Rect.second.x + 4.0f && b.x > Rect.first.x - 4.0f && a.y < Rect.second.y + 2.0f && b.y > Rect.first.y - 2.0f; }))
|
||||
continue;
|
||||
auto const cost{ring + 5.0f * static_cast<float>(tracks_under(a, b))};
|
||||
if (cost < best)
|
||||
{
|
||||
best = cost;
|
||||
from = a;
|
||||
to = b;
|
||||
}
|
||||
}
|
||||
if (best == std::numeric_limits<float>::max())
|
||||
continue;
|
||||
taken.emplace_back(from, to);
|
||||
ImVec2 const middle{(from.x + to.x) * 0.5f, (from.y + to.y) * 0.5f};
|
||||
if (std::hypot(middle.x - nearest.x, middle.y - nearest.y) > 1.0f)
|
||||
{
|
||||
ImVec2 const end{std::clamp(at.x, from.x, to.x), std::clamp(at.y, from.y, to.y)};
|
||||
auto const lead{(label.colour & 0x00ffffffu) | (170u << 24)};
|
||||
drawlist->AddLine(at, end, lead, 1.0f);
|
||||
drawlist->AddCircleFilled(at, 2.5f, lead);
|
||||
}
|
||||
drawlist->AddRectFilled(from, to, IM_COL32(0, 0, 0, 165), 3.0f);
|
||||
drawlist->AddText(ImVec2(from.x + 4.0f, from.y + 2.0f), label.colour, label.text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::draw_track_legend() const
|
||||
{
|
||||
if (m_track_colouring == track_colouring::plain)
|
||||
return;
|
||||
auto const &bands{m_track_colouring == track_colouring::radius ? kRadiusBands : m_track_colouring == track_colouring::speed ? kSpeedBands : kGradeBands};
|
||||
std::vector<std::pair<ImU32, std::string>> rows;
|
||||
for (auto const &band : bands)
|
||||
rows.emplace_back(band.colour, band.label);
|
||||
if (m_track_colouring == track_colouring::radius)
|
||||
rows.emplace_back(kUncoloured, STR("straight"));
|
||||
else if (m_track_colouring == track_colouring::speed)
|
||||
rows.emplace_back(kUncoloured, STR("no limit"));
|
||||
char const *const titles[]{"", "By the radius", "By the speed", "By the grade"};
|
||||
std::string const title{STR_C(titles[static_cast<int>(m_track_colouring)])};
|
||||
auto const line{ImGui::GetTextLineHeightWithSpacing()};
|
||||
auto const swatch{ImGui::GetTextLineHeight()};
|
||||
float width{ImGui::CalcTextSize(title.c_str()).x};
|
||||
for (auto const &row : rows)
|
||||
width = std::max(width, swatch + 6.0f + ImGui::CalcTextSize(row.second.c_str()).x);
|
||||
auto const pad{8.0f};
|
||||
// in the bottom right corner, over the profile strip when it's open
|
||||
auto const bottom{m_profile.strip_top > editor_ui::view_min().y + 4.0f * line ? std::min(editor_ui::view_max().y, m_profile.strip_top) : editor_ui::view_max().y};
|
||||
ImVec2 const to{editor_ui::view_max().x - 12.0f, bottom - 12.0f};
|
||||
ImVec2 const from{to.x - width - pad * 2.0f, to.y - pad * 2.0f - line * static_cast<float>(rows.size() + 1)};
|
||||
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
|
||||
drawlist->AddRectFilled(from, to, IM_COL32(0, 0, 0, 170), 4.0f);
|
||||
ImVec2 at{from.x + pad, from.y + pad};
|
||||
drawlist->AddText(at, IM_COL32(255, 255, 255, 235), title.c_str());
|
||||
for (auto const &row : rows)
|
||||
{
|
||||
at.y += line;
|
||||
drawlist->AddRectFilled(ImVec2(at.x, at.y + 2.0f), ImVec2(at.x + swatch, at.y + swatch - 2.0f), row.first, 2.0f);
|
||||
drawlist->AddText(ImVec2(at.x + swatch + 6.0f, at.y), IM_COL32(235, 235, 235, 235), row.second.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -262,6 +610,11 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
};
|
||||
auto *selected{selected_track()};
|
||||
|
||||
if (m_tape.active)
|
||||
{
|
||||
intent.position = m_tape.mouse;
|
||||
return make(kind::measure, m_tape.points.size() == 1 ? "Click: the end of the measure" : "Click: the start of the measure. M or Esc: done");
|
||||
}
|
||||
if (m_lay.active)
|
||||
{
|
||||
auto const &lay{m_lay};
|
||||
@@ -271,7 +624,7 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
intent.track = lay.mouse_snap.track;
|
||||
intent.position = lay.mouse_snap.position;
|
||||
auto const diverging{lay.mouse_snap.track->eType == tt_Switch && lay.mouse_snap.point == editor_track::point_ref{1, editor_track::point_kind::end}};
|
||||
if (lay.points.empty() && diverging)
|
||||
if (lay.points.empty() && diverging && m_track_layout != track_layout::straight)
|
||||
return make(kind::lay_end, "Click: start from this free end, drag: the curve of the switch goes on");
|
||||
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");
|
||||
}
|
||||
@@ -294,7 +647,17 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
catenary_intent(intent);
|
||||
return intent;
|
||||
}
|
||||
if (m_track_tab == track_tab::signals)
|
||||
if (m_track_tab == track_tab::srk && m_srk.drawing())
|
||||
{
|
||||
bool valid{false};
|
||||
intent.track = m_hover.track;
|
||||
intent.path = m_hover.path;
|
||||
intent.position = m_hover.track != nullptr ? m_hover.point : ground;
|
||||
intent.action = m_srk.boundary_hover(intent.position, valid);
|
||||
intent.what = kind::srk_boundary;
|
||||
return intent;
|
||||
}
|
||||
if (m_track_tab == track_tab::srk)
|
||||
{
|
||||
if (auto const hit{signal_hit()}; hit >= 0)
|
||||
{
|
||||
@@ -305,29 +668,38 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
intent.what = kind::signal_move;
|
||||
return intent;
|
||||
}
|
||||
auto const *armed{signal_armed()};
|
||||
if (m_hover.track != nullptr && armed != nullptr)
|
||||
if (auto const *armed{signal_armed()}; armed != nullptr)
|
||||
{
|
||||
signal_spot spot;
|
||||
if (signal_spot_at(*m_hover.track, m_hover.path, m_hover.point, cursor_level(m_hover.point.y), 0, 0, *armed, spot))
|
||||
if (m_hover.track != nullptr && signal_spot_at(*m_hover.track, m_hover.path, m_hover.point, cursor_level(m_hover.point.y), 0, 0, *armed, spot))
|
||||
{
|
||||
intent.track = m_hover.track;
|
||||
intent.path = m_hover.path;
|
||||
intent.position = spot.position;
|
||||
intent.action = signal_intent_label(*armed, spot);
|
||||
intent.what = kind::signal_place;
|
||||
return intent;
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
if (m_hover.track != nullptr)
|
||||
{
|
||||
intent.track = m_hover.track;
|
||||
intent.path = m_hover.path;
|
||||
intent.position = m_hover.point;
|
||||
return make(kind::select, "Choose the type of the signal in the Tracks window first");
|
||||
return make(kind::select, m_hover.track->eType != tt_Normal ? "Click: the station of this switch" : "Click: the station of the switch nearest along this track");
|
||||
}
|
||||
return intent;
|
||||
}
|
||||
if (m_track_tab == track_tab::kilometrage && m_hover.track != nullptr)
|
||||
{
|
||||
intent.track = m_hover.track;
|
||||
intent.path = m_hover.path;
|
||||
intent.position = m_hover.point;
|
||||
make(kind::kilometrage, "Click: a point of the km here");
|
||||
if (auto const km{m_km.readout(m_hover.point)}; false == km.empty())
|
||||
intent.action = km + "\n" + intent.action;
|
||||
return intent;
|
||||
}
|
||||
if (m_track_style.picking && (m_track_tab == track_tab::path || m_track_tab == track_tab::turnout))
|
||||
{
|
||||
intent.track = m_hover.track;
|
||||
@@ -368,7 +740,7 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
intent.snap.track = selected;
|
||||
intent.snap.point = hit;
|
||||
intent.snap.position = intent.position;
|
||||
auto const diverging{selected->eType == tt_Switch && hit == editor_track::point_ref{1, editor_track::point_kind::end}};
|
||||
auto const diverging{selected->eType == tt_Switch && hit == editor_track::point_ref{1, editor_track::point_kind::end} && m_track_layout != track_layout::straight};
|
||||
return make(kind::extend_end, diverging ? "Drag: the curve of the switch goes on; Ctrl+drag: move the end" : "Drag: a new track out of this end, as in laying; Ctrl+drag: move the end");
|
||||
}
|
||||
}
|
||||
@@ -419,6 +791,18 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
return make(kind::straight_handle, hit == 2 ? "Drag: shift the straight sideways" : "Drag: move this end, the curves at it follow");
|
||||
}
|
||||
}
|
||||
if (m_route.trace.active && route_active())
|
||||
{
|
||||
intent.index = route_trace_hit();
|
||||
if (control && intent.index >= 0)
|
||||
{
|
||||
intent.position = m_route.trace.points[intent.index];
|
||||
return make(kind::route_trace, "Click: take this point out");
|
||||
}
|
||||
intent.index = -1;
|
||||
intent.position = route_trace_cursor();
|
||||
return make(kind::route_trace, m_route.trace.points.empty() ? "Click: a point in the middle of the track on the photo. Enter: apply, Esc: drop" : "Click: one more point, the curve follows. Ctrl+click: take a point out");
|
||||
}
|
||||
if (int vertex, grip; route_hit(vertex, grip))
|
||||
{
|
||||
intent.index = vertex >= 0 ? vertex : grip;
|
||||
@@ -436,11 +820,23 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
return make(kind::straight_tool, m_straights.tool == 1 ? "Press: break the straight here, drag to turn the rest" : "Click: start of the shift");
|
||||
}
|
||||
}
|
||||
// a switch armed, at a free end of a switch, P4 of its diverging track or the end of its main one: the switch goes on from there at
|
||||
// once, as from the free end of a plain track
|
||||
if (m_track_tab == track_tab::turnout && m_hover.track != nullptr && m_hover.track->eType == tt_Switch && free_switch_offered())
|
||||
if (auto const end{free_switch_end(ground)}; end.track == m_hover.track && geometry::plan_distance(end.position, ground) <= 3.0)
|
||||
{
|
||||
intent.position = end.position;
|
||||
return make(kind::switch_free, "Press and drag: a switch going on from this end of the switch, sideways its side");
|
||||
}
|
||||
if ((m_track_tab == track_tab::turnout || m_track_tab == track_tab::path) && m_hover.track != nullptr && switch_slides(*m_hover.track))
|
||||
{
|
||||
intent.track = m_hover.track;
|
||||
intent.path = m_hover.path;
|
||||
intent.position = m_hover.point;
|
||||
// one bent into a curve slides along the curve
|
||||
auto const bent{m_hover.track->eType == tt_Switch && m_hover.track->m_paths.size() == 2 && geometry::has_controls(m_hover.track->m_paths.front())};
|
||||
if (bent)
|
||||
return make(kind::switch_slide, m_track_tab == track_tab::path ? "Click: select this switch, drag: slide it along the curve" : "Click: edit this switch, drag: slide it along the curve");
|
||||
return make(kind::switch_slide, m_track_tab == track_tab::path ? "Click: select this switch, drag: slide it along the straight" : "Click: edit this switch, drag: slide it along the straight");
|
||||
}
|
||||
if (selected != nullptr && false == selected->m_paths.empty())
|
||||
@@ -453,12 +849,29 @@ editor_mode::track_intent editor_mode::track_intent_at(int const Mods)
|
||||
return make(kind::switch_at, "Press and drag: a switch here, along sets its direction, sideways its side");
|
||||
}
|
||||
}
|
||||
if (m_track_tab == track_tab::lineside && m_fouling.open)
|
||||
if (auto const hit{fouling_hit()}; hit.has_value())
|
||||
// a press on another plain track takes it for the switch at once, with no click to select it first; a click only selects it
|
||||
if (m_track_tab == track_tab::turnout && m_switch.armed >= 0 && m_hover.track != nullptr && m_hover.track != selected && m_hover.track->eType == tt_Normal &&
|
||||
false == m_hover.track->m_paths.empty())
|
||||
{
|
||||
intent.track = m_hover.track;
|
||||
intent.path = m_hover.path;
|
||||
intent.position = m_hover.point;
|
||||
return make(kind::switch_at, "Press and drag: a switch on this track, along sets its direction, sideways its side. Click: select the track");
|
||||
}
|
||||
// a press on the ground lays a switch with no track there, going on from a free end within reach
|
||||
if (m_hover.track == nullptr && free_switch_offered())
|
||||
{
|
||||
auto const end{free_switch_end(ground)};
|
||||
intent.position = end.track != nullptr ? end.position : ground;
|
||||
return make(kind::switch_free, end.track != nullptr ? "Press and drag: a switch going on from this free end, sideways its side"
|
||||
: "Press and drag: a switch with no track, along the direction of its main track, then sideways its side");
|
||||
}
|
||||
if (m_track_tab == track_tab::lineside && m_fouling.tab().open)
|
||||
if (auto const hit{m_fouling.hit()}; hit.has_value())
|
||||
{
|
||||
intent.index = hit->first;
|
||||
intent.path = hit->second ? 1 : 0;
|
||||
intent.position = hit->second ? m_fouling.found[hit->first].marker.location : m_fouling.points[hit->first].position;
|
||||
intent.position = m_fouling.position_of(*hit);
|
||||
return make(kind::fouling, hit->second ? "Click: move this W17 to its place" : "Click: put the W17 here");
|
||||
}
|
||||
if (m_hover.track != nullptr)
|
||||
@@ -512,11 +925,82 @@ void editor_mode::update_track_intent()
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::tape_update()
|
||||
{
|
||||
auto &tape{m_tape};
|
||||
if (false == tape.active || ImGui::GetIO().WantCaptureMouse)
|
||||
return;
|
||||
glm::vec2 const cursor{ImGui::GetIO().MousePos.x, ImGui::GetIO().MousePos.y};
|
||||
auto const ground{cursor_ground()};
|
||||
if (auto const end{snap_free_end(ground, cursor, nullptr, kRailCategory, {})}; end.track != nullptr)
|
||||
tape.mouse = end.position;
|
||||
else if (auto const under{track_under_cursor()}; under.track != nullptr)
|
||||
tape.mouse = under.point;
|
||||
else
|
||||
tape.mouse = ground;
|
||||
}
|
||||
|
||||
std::string editor_mode::tape_readout() const
|
||||
{
|
||||
auto const &tape{m_tape};
|
||||
if (tape.points.empty())
|
||||
return {};
|
||||
auto const &from{tape.points.front()};
|
||||
auto const &to{tape.points.size() >= 2 ? tape.points[1] : tape.mouse};
|
||||
auto const plan{geometry::plan_distance(from, to)};
|
||||
auto const rise{to.y - from.y};
|
||||
auto azimuth{glm::degrees(std::atan2(to.x - from.x, to.z - from.z))};
|
||||
if (azimuth < 0.0)
|
||||
azimuth += 360.0;
|
||||
auto text{format(STR_C("%.2f m in plan, %.2f m in space"), plan, glm::distance(from, to))};
|
||||
text += "\n" + format(STR_C("height %+.2f m"), rise) + (plan > 0.01 ? format(STR_C(" grade %+.1f per mille"), rise / plan * 1000.0) : std::string{});
|
||||
text += "\n" + format(STR_C("azimuth %.1f deg"), azimuth);
|
||||
if (tape.points.size() >= 2)
|
||||
text += "\n" + STR("Click: a new measure. M or Esc: done");
|
||||
return text;
|
||||
}
|
||||
|
||||
void editor_mode::draw_tape() const
|
||||
{
|
||||
auto const &tape{m_tape};
|
||||
if (false == tape.active)
|
||||
return;
|
||||
screen_projection const projection;
|
||||
auto *drawlist{ImGui::GetForegroundDrawList(ImGui::GetMainViewport())};
|
||||
auto const colour{IM_COL32(255, 230, 60, 255)};
|
||||
std::vector<glm::dvec3> points{tape.points};
|
||||
if (points.size() < 2)
|
||||
points.push_back(tape.mouse);
|
||||
for (std::size_t i = 0; i + 1 < points.size(); ++i)
|
||||
projection.line(drawlist, points[i], points[i + 1], colour, 2.0f);
|
||||
for (auto const &point : points)
|
||||
{
|
||||
ImVec2 screen;
|
||||
if (projection.project(point, screen))
|
||||
{
|
||||
drawlist->AddCircleFilled(screen, 4.0f, colour);
|
||||
drawlist->AddCircle(screen, 7.0f, IM_COL32(0, 0, 0, 200), 12, 1.5f);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_press(track_intent const &Intent)
|
||||
{
|
||||
using kind = track_intent::kind;
|
||||
switch (Intent.what)
|
||||
{
|
||||
case kind::measure:
|
||||
if (m_tape.points.size() >= 2)
|
||||
m_tape.points.clear();
|
||||
m_tape.points.push_back(m_tape.mouse);
|
||||
return;
|
||||
case kind::route_trace:
|
||||
if (Intent.index >= 0)
|
||||
m_route.trace.points.erase(m_route.trace.points.begin() + Intent.index);
|
||||
else
|
||||
m_route.trace.points.push_back(Intent.position);
|
||||
route_trace_fit();
|
||||
return;
|
||||
case kind::lay_end:
|
||||
if (m_lay.points.empty() && start_extend(Intent.snap))
|
||||
return;
|
||||
@@ -575,12 +1059,33 @@ void editor_mode::track_press(track_intent const &Intent)
|
||||
m_route.grip = Intent.index;
|
||||
handle_drag_start(Intent.position);
|
||||
return;
|
||||
case kind::switch_at: start_switch_placement(); return;
|
||||
case kind::switch_at:
|
||||
{
|
||||
// the press took a track not selected for the switch: let go with no drag it only selects the track
|
||||
if (Intent.track != nullptr && Intent.track != selected_track())
|
||||
select_track(Intent.track);
|
||||
auto const &io{ImGui::GetIO()};
|
||||
m_switch.pressed_at = {io.MousePos.x, io.MousePos.y};
|
||||
start_switch_placement();
|
||||
return;
|
||||
}
|
||||
case kind::switch_free:
|
||||
{
|
||||
auto const &io{ImGui::GetIO()};
|
||||
m_switch.pressed_at = {io.MousePos.x, io.MousePos.y};
|
||||
start_free_switch(Intent.position);
|
||||
return;
|
||||
}
|
||||
case kind::kilometrage:
|
||||
if (Intent.track != nullptr)
|
||||
m_km.pick(*Intent.track, Intent.position);
|
||||
return;
|
||||
case kind::srk_boundary: m_srk.boundary_click(); return;
|
||||
case kind::fouling:
|
||||
if (Intent.path == 1)
|
||||
fouling_fix(Intent.index);
|
||||
m_fouling.fix(Intent.index);
|
||||
else
|
||||
fouling_place(Intent.index);
|
||||
m_fouling.place(Intent.index);
|
||||
return;
|
||||
case kind::extend_end:
|
||||
if (false == start_extend(Intent.snap))
|
||||
@@ -648,6 +1153,7 @@ void editor_mode::draw_track_intent() const
|
||||
case kind::route_vertex:
|
||||
case kind::route_grip: drawlist->AddCircle(at, 13.0f, overlay_color::marked, 24, 2.5f); break;
|
||||
case kind::switch_at:
|
||||
case kind::switch_free:
|
||||
case kind::straight_tool:
|
||||
drawlist->AddCircleFilled(at, 4.0f, overlay_color::grip);
|
||||
drawlist->AddCircle(at, 11.0f, overlay_color::grip, 20, 2.0f);
|
||||
@@ -656,7 +1162,8 @@ void editor_mode::draw_track_intent() const
|
||||
case kind::extend_end:
|
||||
case kind::turntable_fit: drawlist->AddCircleFilled(at, 5.0f, IM_COL32(255, 60, 255, 255)); break;
|
||||
case kind::turntable_exit: drawlist->AddCircle(at, 11.0f, overlay_color::grip, 20, 2.0f); break;
|
||||
case kind::lay: drawlist->AddCircle(at, 6.0f, IM_COL32(255, 255, 255, 200), 12, 1.5f); break;
|
||||
case kind::lay:
|
||||
case kind::route_trace: drawlist->AddCircle(at, 6.0f, IM_COL32(255, 255, 255, 200), 12, 1.5f); break;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -671,10 +1178,26 @@ void editor_mode::draw_track_intent() const
|
||||
details += "\n" + std::string{STR_C(type_of(*track))};
|
||||
if (track->eType == tt_Switch)
|
||||
details += m_hover.path == 0 ? STR(" (main)") : STR(" (branch)");
|
||||
details += format(" L %.1f m", bezier{path}.plan_length());
|
||||
details += path.radius != 0.0f ? format(" R %.0f m", std::abs(path.radius)) : std::string{" "} + STR_C("straight");
|
||||
bezier const curve{path};
|
||||
details += format(" L %.1f m", curve.plan_length());
|
||||
auto const radius{::track_mode::piece_plan_radius(path)};
|
||||
details += radius >= 0.5 ? format(" R %.0f m", radius) : std::string{" "} + STR_C("straight");
|
||||
auto const velocity{editor_track::velocity(*track)};
|
||||
details += velocity > 0.0 ? format(" V %.0f km/h", velocity) : std::string{};
|
||||
double nearest{std::numeric_limits<double>::max()};
|
||||
double at{0.0};
|
||||
for (int k = 0; k <= 64; ++k)
|
||||
if (auto const distance{geometry::plan_distance(curve.point(k / 64.0), m_hover.point)}; distance < nearest)
|
||||
{
|
||||
nearest = distance;
|
||||
at = k / 64.0;
|
||||
}
|
||||
auto const tangent{curve.first(at)};
|
||||
if (auto const run{glm::length(plan_of(tangent))}; run > 1e-9)
|
||||
details += "\n" + format(STR_C("grade %+.1f per mille"), tangent.y / run * 1000.0);
|
||||
auto const roll{std::max(std::abs(path.rolls[0]), std::abs(path.rolls[1]))};
|
||||
if (roll > 1e-3f)
|
||||
details += format(STR_C(" cant %.0f mm"), 1500.0 * std::sin(glm::radians(static_cast<double>(roll))));
|
||||
}
|
||||
auto *foreground{ImGui::GetForegroundDrawList(ImGui::GetMainViewport())};
|
||||
auto const actionsize{ImGui::CalcTextSize(intent.action.c_str())};
|
||||
@@ -716,6 +1239,22 @@ void editor_mode::split_track_at(TTrack &Track, glm::dvec3 const &Point)
|
||||
auto const before{editor_track::capture(Track)};
|
||||
if (auto *created{editor_track::split_path(Track, parameter)})
|
||||
{
|
||||
// a break of the grade, as a point of a road: the point between the two takes the height of the ground and the ballast
|
||||
// there, each of them an even grade from its other end up to it
|
||||
auto const &path{Track.m_paths.front()};
|
||||
auto const joint{path.points[segment_data::point::end]};
|
||||
auto const height{bed_height(joint, geometry::plan_of(geometry::bezier{path}.first(1.0)), &Track)};
|
||||
auto const none{std::numeric_limits<double>::quiet_NaN()};
|
||||
auto const unknown = [](std::vector<glm::dvec3> const &Points, std::vector<glm::dvec2> const &) { return std::vector<double>(Points.size(), std::numeric_limits<double>::quiet_NaN()); };
|
||||
for (auto *part : {&Track, created})
|
||||
{
|
||||
std::vector<segment_data> pieces{part->m_paths.front()};
|
||||
auto &points{pieces.front().points};
|
||||
points[part == &Track ? segment_data::point::end : segment_data::point::start].y = height;
|
||||
ground_follow::follow(pieces, {points[segment_data::point::start].y, none, points[segment_data::point::end].y, none}, unknown);
|
||||
part->m_paths.front() = pieces.front();
|
||||
}
|
||||
editor_track::commit({&Track, created});
|
||||
push_track_snapshot({{&Track, before}}, {created});
|
||||
straight_refresh();
|
||||
}
|
||||
@@ -787,11 +1326,13 @@ void editor_mode::render_track_context()
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(STR_C("Split here"), "K", false, normal))
|
||||
if (ImGui::MenuItem(STR_C("Split here, a break of the grade"), "K", false, normal))
|
||||
split_track_at(*track, point);
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(STR_C("Switch"), "T", false, normal || track->eType == tt_Switch))
|
||||
show_track_tab(track_tab::turnout);
|
||||
if (track->eType == tt_Switch && track->SwitchExtension != nullptr && ImGui::MenuItem(STR_C("Throw it over"), "H"))
|
||||
throw_switch(*track);
|
||||
if (ImGui::MenuItem(STR_C("Plan"), "G", false, normal))
|
||||
show_track_tab(track_tab::straights);
|
||||
if (ImGui::MenuItem(STR_C("Parallel track"), "N", false, normal))
|
||||
@@ -802,19 +1343,17 @@ void editor_mode::render_track_context()
|
||||
if (ImGui::MenuItem(STR_C("Place a vehicle here")))
|
||||
{
|
||||
show_track_tab(track_tab::lineside);
|
||||
vehicle_start(*track, point);
|
||||
m_vehicle.start(*track, point);
|
||||
}
|
||||
if (ImGui::MenuItem(STR_C("Hectometre posts from here")))
|
||||
{
|
||||
show_track_tab(track_tab::lineside);
|
||||
hekto_start(*track, point);
|
||||
m_hekto.start(*track, point);
|
||||
}
|
||||
if (ImGui::MenuItem(STR_C("Fouling point markers"), nullptr, false, track->eType == tt_Switch))
|
||||
{
|
||||
show_track_tab(track_tab::lineside);
|
||||
m_fouling.scope = 0;
|
||||
m_fouling.dirty = true;
|
||||
m_fouling.expand = true;
|
||||
m_fouling.check_selected_switch();
|
||||
}
|
||||
if (ImGui::MenuItem(STR_C("Vertical profile of the line"), "P"))
|
||||
{
|
||||
@@ -823,9 +1362,8 @@ void editor_mode::render_track_context()
|
||||
}
|
||||
if (ImGui::MenuItem(STR_C("Speed check of the line"), "V"))
|
||||
{
|
||||
m_speed.scope = 0;
|
||||
show_track_tab(track_tab::speed);
|
||||
speed_start();
|
||||
m_speed.check_line();
|
||||
}
|
||||
ImGui::Separator();
|
||||
if (ImGui::MenuItem(track_selected(track) ? STR_C("Take out of the selection") : STR_C("Add to the selection"), STR_C("Shift+LMB")))
|
||||
@@ -855,7 +1393,7 @@ void editor_mode::render_track_search()
|
||||
}
|
||||
ImGui::SetNextItemWidth(-1.0f);
|
||||
ImGui::InputTextWithHint("##findtrack", STR_C("Find a track by its name (Ctrl+F)"), search.text, sizeof(search.text));
|
||||
std::string const text{lower(search.text)};
|
||||
std::string const text{ToLower(search.text)};
|
||||
if (text.empty())
|
||||
{
|
||||
search.searched.clear();
|
||||
@@ -869,7 +1407,7 @@ void editor_mode::render_track_search()
|
||||
search.total = 0;
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || false == rail(*track) || lower(track->name()).find(text) == std::string::npos)
|
||||
if (track == nullptr || false == rail(*track) || ToLower(track->name()).find(text) == std::string::npos)
|
||||
continue;
|
||||
++search.total;
|
||||
if (search.found.size() < kSearchLimit)
|
||||
@@ -915,6 +1453,8 @@ char const *editor_mode::typed_meaning() const
|
||||
return "type the length of the next leg, Enter adds the point";
|
||||
if (m_track_tab == track_tab::catenary && catenary_takes_typed())
|
||||
return "type the span from the last pole, LMB by the track places the pole there";
|
||||
if (m_extend.active && (m_track_layout == track_layout::arc_straight || m_track_layout == track_layout::straight_arc))
|
||||
return "type the radius of the curve";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
@@ -973,7 +1513,7 @@ bool editor_mode::typed_key(int const Key)
|
||||
glm::dvec3 editor_mode::typed_lay(glm::dvec3 const &Mouse) const
|
||||
{
|
||||
auto const values{typed_values()};
|
||||
if (values.empty() || m_lay.points.empty() || values[0] <= 0.0)
|
||||
if (values.empty() || m_lay.points.empty() || false == (values[0] > 0.0 && values[0] <= 50000.0))
|
||||
return Mouse;
|
||||
auto const &last{m_lay.points.back()};
|
||||
glm::dvec2 heading{Mouse.x - last.x, Mouse.z - last.z};
|
||||
@@ -1348,7 +1888,9 @@ void editor_mode::track_set_delete()
|
||||
if (std::find(m_route.chain.tracks.begin(), m_route.chain.tracks.end(), track) != m_route.chain.tracks.end())
|
||||
m_route = {};
|
||||
}
|
||||
auto const designs{designs_trimmed(removed)};
|
||||
push_track_snapshot({}, {}, removed);
|
||||
drives_deleted(removed);
|
||||
m_track_set.clear();
|
||||
m_node = nullptr;
|
||||
ui()->set_node(nullptr);
|
||||
@@ -1357,6 +1899,8 @@ void editor_mode::track_set_delete()
|
||||
auto status{format(STR_P("%zu track deleted, Ctrl+Z brings it back", "%zu tracks deleted, Ctrl+Z brings them back", static_cast<long long>(removed.size())), removed.size())};
|
||||
if (kept > 0)
|
||||
status += format(STR_C(", %zu with vehicles on them or in the files which can't be changed are left"), kept);
|
||||
if (false == designs.empty())
|
||||
status += "\n" + designs;
|
||||
ui()->set_status(status, false);
|
||||
}
|
||||
|
||||
@@ -1369,7 +1913,7 @@ void editor_mode::render_track_set_ui()
|
||||
ImGui::PushTextWrapPos(0.0f);
|
||||
ImGui::TextDisabled("%s", STR_C("Their properties are in the inspector, the gizmo moves them together"));
|
||||
ImGui::PopTextWrapPos();
|
||||
render_parallel_set_ui();
|
||||
m_parallel.render_set_options();
|
||||
ImGui::Separator();
|
||||
}
|
||||
|
||||
@@ -1489,9 +2033,9 @@ void editor_mode::render_lineside_ui()
|
||||
std::function<void()> render;
|
||||
};
|
||||
std::array<tab, 3> const tabs{{
|
||||
{"Hectometre posts", &m_hekto.open, &m_hekto.expand, [this]() { render_hekto_ui(); }},
|
||||
{"Fouling points", &m_fouling.open, &m_fouling.expand, [this]() { render_fouling_ui(); }},
|
||||
{"Trainset", &m_vehicle.open, &m_vehicle.expand, [this]() { render_vehicle_ui(); }},
|
||||
{"Hectometre posts", &m_hekto.tab().open, &m_hekto.tab().expand, [this]() { m_hekto.render_options(); }},
|
||||
{"Fouling points", &m_fouling.tab().open, &m_fouling.tab().expand, [this]() { m_fouling.render_options(); }},
|
||||
{"Trainset", &m_vehicle.tab().open, &m_vehicle.tab().expand, [this]() { m_vehicle.render_options(); }},
|
||||
}};
|
||||
if (selected_track() == nullptr)
|
||||
ImGui::TextColored(ImVec4(1.0f, 0.8f, 0.3f, 1.0f), "%s", STR_C("LMB on a track: the line the objects go along"));
|
||||
|
||||
Reference in New Issue
Block a user