17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-23 14:55:15 +02:00

feat(editor): Alt while laying turns the straight to 1:9, 1:6.6... of a straight track

Besides the steps of 15 degrees, Alt snaps the laid straight to the
ratios of the switches (1:4.8 to 1:26.5) of the straight track the
straight crosses, or of one within 30 m of where it starts; the label at
the cursor says which ratio to which track. The first click with Alt
within 2 m of a straight track puts the start on its axis, so a branch
or a crossing at 1:9 starts exactly on the track.
This commit is contained in:
maj00r
2026-10-19 18:48:54 +02:00
parent 438cb4ea3a
commit 907068c69c
6 changed files with 106 additions and 17 deletions

View File

@@ -5079,7 +5079,7 @@ void editor_mode::render_first_steps()
"curves",
{{"Ctrl + wheel", "The radius of the curve, while laying on from an end"},
{"Shift + wheel", "The shape: arc then straight, straight then arc, arc, straight"},
{"Alt", "Held: the direction turns in steps of 15 degrees"},
{"Alt", "Held: the direction turns in steps of 15 degrees, or to 1:9, 1:6.6... to a straight track it starts by or crosses"},
{"0 1 2 3", "Type the length of the next piece, Enter lays it"},
{"wheel", "Alone it zooms, as everywhere"}},
{{first_step::wheel, "Change the radius with Ctrl+wheel"}, {first_step::shape, "Change the shape with Shift+wheel"}},
@@ -5425,7 +5425,7 @@ void editor_mode::render_shortcuts_window()
{"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"},
{"Alt held while laying", "The direction in steps of 15 degrees"},
{"Alt held while laying", "The direction in steps of 15 degrees, or 1:9, 1:6.6... to a straight track; the first click with Alt goes onto the axis of the track"},
{"N", "Parallel track: LMB on a track, then the side and the spacing"},
{"Shift+LMB on a track in L", "The Parallel tool (N) with it"},
{"Enter / Backspace", "Laying track: lay up to the cursor / take the last piece back"},

View File

@@ -1343,6 +1343,7 @@ class editor_mode : public application_mode, private editor_track::observer
editor_track::path_style style;
std::string status;
double status_time{-100.0};
std::string skew; // what Alt turned the straight to
struct step
{
editor_track::snap_target start;
@@ -1354,7 +1355,9 @@ class editor_mode : public application_mode, private editor_track::observer
bool m_norms_free{true};
alignment::limits laying_norms() const;
bool render_norms_choice();
glm::dvec3 lay_angle_snap(glm::dvec3 const &Mouse) const;
glm::dvec3 lay_angle_snap(glm::dvec3 const &Mouse, std::string &Label) const;
// the straight track Alt turns the laid straight to: the nearest one the straight from From to To crosses, or one along From
TTrack const *skew_reference(glm::dvec3 const &From, glm::dvec3 const &To, double const Reach) const;
void lay_after_history(bool const Undo);
void lay_back();
void render_lay_ui();

View File

@@ -80,8 +80,9 @@ void editor_mode::update_build_tools()
double const height{false == lay.points.empty() ? lay.points.back().y : lay.start.track != nullptr ? lay.start.position.y : ground.y};
if (pointing)
lay.mouse = typed_lay(cursor_terrain(height));
lay.skew.clear();
if (pointing && ImGui::GetIO().KeyAlt && m_typed.empty() && false == lay.points.empty())
lay.mouse = lay_angle_snap(lay.mouse);
lay.mouse = lay_angle_snap(lay.mouse, lay.skew);
lay.mouse_snap = m_typed.empty() ? snap_free_end(lay.mouse, cursor, nullptr, kRailCategory, {}) : editor_track::find_free_end(nullptr, kRailCategory, lay.mouse, kLaySnapRadius, {});
if (lay.mouse_snap.track == nullptr)
lay.mouse = lay_bed(lay.mouse);
@@ -926,7 +927,16 @@ void editor_mode::lay_click()
{
tool.chain.clear();
tool.start = snap;
tool.points.push_back(snap.track != nullptr ? snap.position : tool.mouse);
auto point{snap.track != nullptr ? snap.position : tool.mouse};
if (auto const *axis{snap.track == nullptr && ImGui::GetIO().KeyAlt ? skew_reference(point, point, kSkewSeat) : nullptr})
{
auto const &path{axis->m_paths.front()};
auto const &from{path.points[segment_data::point::start]};
auto const &to{path.points[segment_data::point::end]};
auto const along{std::clamp(glm::dot(plan_of(point - from), plan_of(to - from)) / glm::dot(plan_of(to - from), plan_of(to - from)), 0.0, 1.0)};
point = from + (to - from) * along;
}
tool.points.push_back(point);
return;
}
auto const &point{tool.mouse};
@@ -1093,20 +1103,84 @@ void editor_mode::lay_back()
tool.start = {};
}
glm::dvec3 editor_mode::lay_angle_snap(glm::dvec3 const &Mouse) const
glm::dvec3 editor_mode::lay_angle_snap(glm::dvec3 const &Mouse, std::string &Label) const
{
Label.clear();
auto const &last{m_lay.points.back()};
glm::dvec2 const offset{Mouse.x - last.x, Mouse.z - last.z};
auto const length{glm::length(offset)};
if (length < 1.0)
return Mouse;
auto const leaving{m_lay.start.track != nullptr ? -plan_of(m_lay.start.direction) : glm::dvec2{0.0}};
glm::dvec2 const reference{glm::length(leaving) > 1e-6 ? glm::normalize(leaving) : glm::dvec2{0.0, 1.0}};
glm::dvec2 reference{glm::length(leaving) > 1e-6 ? glm::normalize(leaving) : glm::dvec2{0.0, 1.0}};
auto const step{glm::radians(15.0)};
auto const heading{turned(reference, std::round(signed_angle(reference, offset / length) / step) * step) * length};
auto angle{std::round(signed_angle(reference, offset / length) / step) * step};
if (auto const *axis{skew_reference(last, Mouse, kSkewReach)})
{
auto const &path{axis->m_paths.front()};
reference = glm::normalize(plan_of(path.points[segment_data::point::end] - path.points[segment_data::point::start]));
auto const relative{signed_angle(reference, offset / length)};
angle = std::round(relative / step) * step;
double ratio{0.0};
for (auto const n : kSkewRatios)
for (auto const base : {0.0, glm::pi<double>()})
for (auto const side : {-1.0, 1.0})
{
auto const candidate{base + side * std::atan(1.0 / n)};
if (std::abs(std::remainder(relative - candidate, 2.0 * glm::pi<double>())) < std::abs(std::remainder(relative - angle, 2.0 * glm::pi<double>())))
{
angle = candidate;
ratio = n;
}
}
auto const name{axis->name().empty() ? std::string{STR_C("(no name)")} : axis->name()};
auto const degrees{glm::degrees(std::abs(std::remainder(angle, glm::pi<double>())))};
Label = ratio > 0.0 ? format(STR_C("1:%g to %s"), ratio, name.c_str()) : degrees < 0.1 ? format(STR_C("parallel to %s"), name.c_str()) : format(STR_C("%.0f deg to %s"), degrees, name.c_str());
}
auto const heading{turned(reference, angle) * length};
return {last.x + heading.x, Mouse.y, last.z + heading.y};
}
TTrack const *editor_mode::skew_reference(glm::dvec3 const &From, glm::dvec3 const &To, double const Reach) const
{
auto const from{plan_of(From)};
auto const leg{plan_of(To) - from};
auto const length{glm::length(leg)};
TTrack const *nearest{nullptr};
auto best{std::numeric_limits<double>::max()};
for (auto *section : simulation::Region->sections((From + To) * 0.5, static_cast<float>(std::min(0.5 * length + Reach, 2000.0))))
for (auto const &cell : section->m_cells)
for (auto *track : cell.m_directories.paths)
{
if (track == nullptr || track->eType != tt_Normal || track->m_editorremoved || (track->iCategoryFlag & 15) != kRailCategory || track->m_paths.size() != 1 ||
false == editor_track::is_straight(*track, m_straights.tolerance))
continue;
auto const &path{track->m_paths.front()};
auto const a{plan_of(path.points[segment_data::point::start])};
auto const axis{plan_of(path.points[segment_data::point::end]) - a};
auto const span{glm::length(axis)};
if (span < 1.0)
continue;
auto const along{std::clamp(glm::dot(from - a, axis) / span, 0.0, span)};
auto distance{glm::distance(from, a + axis / span * along)};
if (distance > Reach)
distance = std::numeric_limits<double>::max();
if (auto const determinant{cross(leg, axis)}; length > 1.0 && std::abs(determinant) > 1e-9)
{
auto const t{cross(a - from, axis) / determinant};
auto const u{cross(a - from, leg) / determinant};
if (t >= 0.0 && t <= 1.0 && u >= 0.0 && u <= 1.0)
distance = std::min(distance, t * length);
}
if (distance < best)
{
best = distance;
nearest = track;
}
}
return nearest;
}
editor_mode::track_hover editor_mode::split_spot() const
{
auto const *selected{selected_track()};

View File

@@ -1521,14 +1521,14 @@ std::vector<editor_mode::key_hint> editor_mode::track_key_hints(bool const All)
if (m_lay.active)
{
if (m_lay.points.empty())
hints = {{"LMB", "start, on the ground or at a free end"}, {"Drag from a free end", "lays the shape to the cursor"}, {"Shift+click a straight", "a parallel track (N)"}, {"Alt", "the direction in steps of 15 degrees"}, {"Esc", "stop laying"}};
hints = {{"LMB", "start, on the ground or at a free end"}, {"Drag from a free end", "lays the shape to the cursor"}, {"Shift+click a straight", "a parallel track (N)"}, {"Alt", "the direction in steps of 15 degrees, or 1:9, 1:6.6... to a straight track"}, {"Esc", "stop laying"}};
else
{
bool const chained{m_lay.start.track != nullptr};
hints = {{"LMB", chained ? "lay the shape up to here" : "lay the straight up to here"}, {"LMB on a free end", "join and lay"}};
if (chained || m_lay.mouse_snap.track != nullptr)
hints.insert(hints.end(), {{"Ctrl+wheel", "radius"}, {"Shift+wheel", "another shape"}});
hints.insert(hints.end(), {{"Alt", "the direction in steps of 15 degrees"}, {"Enter", "lay up to the cursor"}, {"Backspace", m_lay.chain.empty() ? "take back the point" : "take back the last piece"}, {"Esc", "cancel"}});
hints.insert(hints.end(), {{"Alt", "the direction in steps of 15 degrees, or 1:9, 1:6.6... to a straight track"}, {"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())
@@ -1738,7 +1738,7 @@ std::string editor_mode::track_readout() const
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);
if (ImGui::GetIO().KeyAlt && false == lay.points.empty())
text += "\n" + format(STR_C("azimuth %.0f deg"), azimuth(lay.points.back(), lay.mouse));
text += "\n" + (lay.skew.empty() ? format(STR_C("azimuth %.0f deg"), azimuth(lay.points.back(), lay.mouse)) : lay.skew);
if (false == lay.status.empty() && ImGui::GetTime() - lay.status_time < 4.0)
text += "\n" + lay.status;
return text;

View File

@@ -104,6 +104,9 @@ double const kLaySnapRadius{5.0};
float const kSnapPixels{16.0f};
double const kSwitchReach{6.0}; // a press farther from the selected track selects another one instead of placing the switch
double const kSlipReach{12.0}; // m; a slip goes to the crossing nearest the cursor, if it's this close
double const kSkewSeat{2.0}; // m; the first point laid with Alt this close to a straight track goes onto its axis
double const kSkewReach{30.0}; // m; Alt while laying turns the straight to a straight track this close to its start, or one it crosses
double const kSkewRatios[]{4.8, 6.6, 7.5, 9.0, 12.0, 14.0, 18.5, 26.5};
double const kLineReach{2000.0}; // how far each way the Curve tab follows the line
double const kDigitizeFit{0.5};
double const kDrawnReach{100000.0};

View File

@@ -4590,14 +4590,14 @@ msgstr "K: podziel tutaj"
msgid "Shift+click a straight"
msgstr "Shift+LPM na prostej"
msgid "the direction in steps of 15 degrees"
msgstr "kierunek co 15 stopni"
msgid "the direction in steps of 15 degrees, or 1:9, 1:6.6... to a straight track"
msgstr "kierunek co 15 stopni albo 1:9, 1:6,6… do prostego toru"
msgid "Alt held while laying"
msgstr "Wciśnięty Alt w trakcie układania"
msgid "The direction in steps of 15 degrees"
msgstr "Kierunek co 15 stopni"
msgid "The direction in steps of 15 degrees, or 1:9, 1:6.6... to a straight track; the first click with Alt goes onto the axis of the track"
msgstr "Kierunek co 15 stopni albo 1:9, 1:6,6… do prostego toru; pierwszy klik z Altem trafia w oś toru"
msgid "Shift+LMB on a track in L"
msgstr "Shift+LPM na torze przy układaniu (L)"
@@ -5376,8 +5376,8 @@ msgstr "Promień łuku, gdy układasz dalej od końca toru"
msgid "The shape: arc then straight, straight then arc, arc, straight"
msgstr "Kształt: łuk i prosta, prosta i łuk, sam łuk, prosta"
msgid "Held: the direction turns in steps of 15 degrees"
msgstr "Trzymany: kierunek zmienia się co 15 stopni"
msgid "Held: the direction turns in steps of 15 degrees, or to 1:9, 1:6.6... to a straight track it starts by or crosses"
msgstr "Trzymany: kierunek zmienia się co 15 stopni albo na 1:9, 1:6,6… do prostego toru, przy którym się zaczyna albo który przecina"
msgid "Type the length of the next piece, Enter lays it"
msgstr "Wpisz długość następnego kawałka, Enter go układa"
@@ -9934,3 +9934,12 @@ msgstr "Klik: ten rozjazd krzyżowy zostanie zastąpiony wybranym"
msgid "Click: it goes here"
msgstr "Klik: stanie tutaj"
msgid "1:%g to %s"
msgstr "1:%g do %s"
msgid "parallel to %s"
msgstr "równolegle do %s"
msgid "%.0f deg to %s"
msgstr "%.0f° do %s"