mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 23:23:30 +02:00
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.
239 lines
9.4 KiB
C++
239 lines
9.4 KiB
C++
/*
|
|
This Source Code Form is subject to the
|
|
terms of the Mozilla Public License, v.
|
|
2.0. If a copy of the MPL was not
|
|
distributed with this file, You can
|
|
obtain one at
|
|
http://mozilla.org/MPL/2.0/.
|
|
*/
|
|
|
|
#pragma once
|
|
|
|
// what the files of the track mode of the editor share: the sizes of the handles and reaches, and small helpers
|
|
|
|
#include "editor/editorAlignment.hpp"
|
|
#include "editor/editorFormat.hpp"
|
|
#include "editor/editorGeometry.hpp"
|
|
#include "editor/editorTrack.hpp"
|
|
#include "utilities/translation.h"
|
|
#include "world/Track.h"
|
|
|
|
#include "imgui/imgui.h"
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace track_mode
|
|
{
|
|
|
|
using geometry::bezier;
|
|
using geometry::cross;
|
|
using geometry::grade_of;
|
|
using geometry::plan_distance;
|
|
using geometry::plan_of;
|
|
using geometry::signed_angle;
|
|
using geometry::turned;
|
|
using geometry::arc_pieces;
|
|
using geometry::arc_through;
|
|
using geometry::has_controls;
|
|
using geometry::level;
|
|
using geometry::reversed;
|
|
using geometry::straight_piece;
|
|
|
|
double constexpr kParallelFindAngle{15.0}; // deg, neighbours counted as beside the straight
|
|
double constexpr kParallelSnapAngle{5.0}; // deg, close enough to snap parallel
|
|
double constexpr kParallelSnapWindow{0.45}; // m from 4.00 / 4.50 / 4.75 m
|
|
double constexpr kParallelHintWindow{1.25}; // m, hint before the snap takes
|
|
|
|
inline double piece_plan_radius(segment_data const &Path)
|
|
{
|
|
if (std::abs(Path.radius) > 0.5f)
|
|
return std::abs(Path.radius);
|
|
bezier const curve{Path};
|
|
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{std::abs(signed_angle(begin, finish))};
|
|
if (turn < 1e-4)
|
|
return 0.0;
|
|
return curve.plan_length() / turn;
|
|
}
|
|
|
|
inline 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;
|
|
}
|
|
|
|
inline 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);
|
|
}
|
|
|
|
inline std::string describe(editor_track::straight const &Line)
|
|
{
|
|
return format(STR_C("L %.2f m azimuth %.4f deg grade %.2f per mille %zu paths"), Line.length, Line.azimuth, Line.grade * 1000.0, Line.tracks.size());
|
|
}
|
|
|
|
inline char const *point_label(editor_track::point_kind const Kind)
|
|
{
|
|
switch (Kind)
|
|
{
|
|
case editor_track::point_kind::start: return STR_C("Start");
|
|
case editor_track::point_kind::control1: return STR_C("Control 1");
|
|
case editor_track::point_kind::control2: return STR_C("Control 2");
|
|
default: return STR_C("End");
|
|
}
|
|
}
|
|
|
|
editor_track::point_kind const kPointKinds[] = {editor_track::point_kind::start, editor_track::point_kind::control1, editor_track::point_kind::control2, editor_track::point_kind::end};
|
|
|
|
float const kHandleRadius{10.0f};
|
|
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};
|
|
double const kJointRange{400.0}; // the ends of the paths are drawn for the tracks this close to the camera
|
|
double const kDotsRange{500.0}; // m, the dots of the ends of the tracks are drawn this close to the camera in the 3D view
|
|
ImU32 const kJointColor{IM_COL32(255, 255, 255, 200)};
|
|
int const kRailCategory{1};
|
|
|
|
inline editor_track::branch_end branch_start(segment_data const &Diverging)
|
|
{
|
|
auto const tangent{bezier{Diverging}.first(1.0)};
|
|
return {Diverging.points[segment_data::point::end], plan_of(tangent), grade_of(tangent), static_cast<double>(Diverging.rolls[1])};
|
|
}
|
|
|
|
inline editor_track::branch_end branch_target(editor_track::snap_target const &Target)
|
|
{
|
|
return {Target.position, plan_of(Target.direction), grade_of(Target.direction), Target.track != nullptr ? editor_track::joint_roll(*Target.track, Target.point) : 0.0};
|
|
}
|
|
|
|
inline double switch_from(editor_track::straight const &Line, double const Length, double const Along, int const Direction, bool const Snap)
|
|
{
|
|
auto from{std::clamp(Direction > 0 ? Along : Along - Length, 0.0, Line.length - Length)};
|
|
if (false == Snap)
|
|
return from;
|
|
std::vector<double> joints{0.0, Line.length};
|
|
for (std::size_t i = 0; i < Line.tracks.size(); ++i)
|
|
{
|
|
auto const &path{Line.tracks[i]->m_paths[Line.paths[i]]};
|
|
for (auto const index : {segment_data::point::start, segment_data::point::end})
|
|
joints.push_back(Line.along(path.points[index]));
|
|
}
|
|
auto const nearest = [&](double const Value) {
|
|
double best{Value};
|
|
for (auto const joint : joints)
|
|
if (std::abs(joint - Value) < std::abs(best - Value) || best == Value)
|
|
best = std::abs(joint - Value) < 1.0 ? joint : best;
|
|
return best;
|
|
};
|
|
auto const snappedstart{nearest(from)};
|
|
auto const snappedend{nearest(from + Length)};
|
|
if (snappedstart != from)
|
|
from = snappedstart;
|
|
else if (snappedend != from + Length)
|
|
from = snappedend - Length;
|
|
return std::clamp(from, 0.0, Line.length - Length);
|
|
}
|
|
|
|
inline std::string link_summary(editor_track::snap_target const &Target, editor_track::branch_link const &Link, double const Switchradius)
|
|
{
|
|
auto const name{Target.track != nullptr && false == Target.track->name().empty() ? Target.track->name() : std::string{STR_C("(no name)")}};
|
|
auto text{Link.reverse ? format(STR_C("The branch leads into %s with reverse curves R %.0f m"), name.c_str(), Link.radius) :
|
|
Link.radius > 0.0 ? format(STR_C("The branch leads into %s with an R %.0f m curve"), name.c_str(), Link.radius) : format(STR_C("The branch leads straight into %s"), name.c_str())};
|
|
if (Link.insert > 0.0)
|
|
text += format(STR_C(", a straight of %.1f m between them"), Link.insert);
|
|
if (Link.reverse && Link.radius < Switchradius - 0.5)
|
|
text += format(STR_C(", sharper than the switch (R %.0f m)"), Switchradius);
|
|
if (Link.trim > 0.0)
|
|
text += format(STR_C(", %s shortened %.2f m"), name.c_str(), Link.trim);
|
|
else if (Link.trim < 0.0)
|
|
text += format(STR_C(", %s lengthened %.2f m"), name.c_str(), -Link.trim);
|
|
return text;
|
|
}
|
|
|
|
inline std::optional<alignment::join_result> eased_join(alignment::join_request const &Request, editor_track::easing const &In, editor_track::easing const &Out)
|
|
{
|
|
if (In.pieces.empty() && Out.pieces.empty())
|
|
return std::nullopt;
|
|
auto middle{Request};
|
|
if (false == In.pieces.empty())
|
|
{
|
|
middle.start.position = In.end;
|
|
middle.start.direction = In.direction;
|
|
middle.start.roll = In.pieces.back().rolls[1];
|
|
}
|
|
if (false == Out.pieces.empty())
|
|
{
|
|
middle.end.position = Out.end;
|
|
middle.end.direction = -Out.direction;
|
|
middle.end.roll = -Out.pieces.back().rolls[1];
|
|
}
|
|
if (false == editor_track::eases_room(In, Out, Request.start.position, Request.start.direction, Request.end.position, Request.end.direction))
|
|
return std::nullopt;
|
|
auto eased{alignment::join(middle)};
|
|
if (eased.kind == alignment::join_kind::none || eased.kind == alignment::join_kind::bezier)
|
|
return std::nullopt;
|
|
eased.length = editor_track::eased_length(In, eased.length, Out);
|
|
eased.pieces = editor_track::eased_pieces(In, eased.pieces, Out);
|
|
return eased;
|
|
}
|
|
|
|
// curvature of the shape at Chainage, positive turning from x towards z
|
|
inline double curvature_at(alignment::result const &Shape, double const Chainage)
|
|
{
|
|
auto const a{std::clamp(Chainage - 0.5, 0.0, Shape.length)};
|
|
auto const b{std::clamp(Chainage + 0.5, 0.0, Shape.length)};
|
|
if (b - a < 1e-3)
|
|
return 0.0;
|
|
auto const from{plan_of(alignment::evaluate(Shape, a).direction)};
|
|
auto const to{plan_of(alignment::evaluate(Shape, b).direction)};
|
|
return signed_angle(glm::normalize(from), glm::normalize(to)) / (b - a);
|
|
}
|
|
|
|
// circular curve from a point along a direction, cut into pieces of 90 degrees at most. Side: 1 to the left, -1 to the right
|
|
inline void compound_from(editor_track::curve const &Curve, alignment::vertex &Vertex)
|
|
{
|
|
if (false == Curve.compound || Curve.arcs.size() < 2)
|
|
return;
|
|
Vertex.compound = true;
|
|
Vertex.radius = std::round(Curve.arcs.front().radius);
|
|
Vertex.share = Curve.arcs.front().turn;
|
|
Vertex.arcs.clear();
|
|
for (std::size_t i = 1; i < Curve.arcs.size(); ++i)
|
|
Vertex.arcs.push_back({std::round(Curve.arcs[i].radius), std::round(Curve.arcs[i].transition), Curve.arcs[i].turn});
|
|
}
|
|
|
|
inline char const *type_name(TTrack const &Track)
|
|
{
|
|
switch (Track.eType)
|
|
{
|
|
case tt_Normal: return "normal";
|
|
case tt_Switch: return "switch";
|
|
case tt_Cross: return "cross";
|
|
case tt_Table: return "turntable";
|
|
case tt_Tributary: return "tributary";
|
|
default: return "unknown type";
|
|
}
|
|
}
|
|
|
|
} // namespace track_mode
|