mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-20 15:04:54 +02:00
Labels: - every button, field, tooltip and message of the editor reread for a beginner: short nouns for the fields with the sentence in the tooltip, the less used options under More settings, one glossary (field of work, template, track, grade), sides told from directions, degrees and per mille as signs, messages built from one format string - plural forms through STR_P; the last entry of a po file is read too, and the Polish plural rule is in the header - lang/editor_pl.po: the code pass entries merged, 391 entries no code uses and 3 duplicates dropped - roads: the notes and errors naming a road translated; the build options and the layout fold the texture length, kerb sizes, traffic share, roundabout part, height over the ground and junction speed limit under More settings, the lanes start folded Grade lines fitted to the terrain model (NMT), a line or the whole scenery at once: - fit_ground: a robust fit on nodes 5 m apart; the ground parting from the line (a bridge, a viaduct, a wall) is gone past rather than followed, the bends keep off the switches as far as their curves reach, a run of bends the same way is one point of intersection, a curve with no room is as sharp as the room lets it be - the ends of the line and the other paths of its switches leading into lines fitted already bound the line by what the tracks between can climb; ends meeting in the plan, laid apart in height by hand, are joined before the run - apply_profile: the paths are split where split_path takes the parameter (a straight was cut in the wrong place, into pieces of a few centimetres the game takes for zero length), no piece shorter than the margin; the paths off the ends of the route are brought to its height - Bytom: 1600 lines, grade breaks at the joints 89 -> 20; Tarnowskie Gory: 848 lines, 223 -> 3; no step over 2 cm
299 lines
9.0 KiB
C++
299 lines
9.0 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/.
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
#include "editor/editorInfra.hpp"
|
|
|
|
#include "editor/editorGeometry.hpp"
|
|
#include "world/Track.h"
|
|
|
|
#include <algorithm>
|
|
#include <cctype>
|
|
#include <cmath>
|
|
#include <sstream>
|
|
|
|
namespace infra
|
|
{
|
|
|
|
namespace
|
|
{
|
|
|
|
using geometry::bezier;
|
|
using geometry::plan_distance;
|
|
using geometry::plan_of;
|
|
|
|
int const kSteps{128};
|
|
|
|
// arc length in the plan at the evenly spread parameters
|
|
std::array<double, kSteps + 1> lengths(bezier const &Curve)
|
|
{
|
|
std::array<double, kSteps + 1> result{};
|
|
auto previous{Curve.p0};
|
|
for (int i = 1; i <= kSteps; ++i)
|
|
{
|
|
auto const next{Curve.point(static_cast<double>(i) / kSteps)};
|
|
result[i] = result[i - 1] + plan_distance(previous, next);
|
|
previous = next;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
double parameter(std::array<double, kSteps + 1> const &Lengths, double const S)
|
|
{
|
|
if (S <= 0.0)
|
|
return 0.0;
|
|
if (S >= Lengths.back())
|
|
return 1.0;
|
|
auto const next{std::upper_bound(Lengths.begin(), Lengths.end(), S)};
|
|
auto const index{static_cast<int>(std::distance(Lengths.begin(), next)) - 1};
|
|
auto const run{Lengths[index + 1] - Lengths[index]};
|
|
auto const fraction{run > 1e-12 ? (S - Lengths[index]) / run : 0.0};
|
|
return (index + fraction) / kSteps;
|
|
}
|
|
|
|
double length_at(std::array<double, kSteps + 1> const &Lengths, double const T)
|
|
{
|
|
auto const position{std::clamp(T, 0.0, 1.0) * kSteps};
|
|
auto const index{std::min(static_cast<int>(position), kSteps - 1)};
|
|
return Lengths[index] + (Lengths[index + 1] - Lengths[index]) * (position - index);
|
|
}
|
|
|
|
glm::dvec2 plan_direction(bezier const &Curve, double const T)
|
|
{
|
|
auto tangent{Curve.first(T)};
|
|
glm::dvec2 result{tangent.x, tangent.z};
|
|
if (glm::length(result) < 1e-9)
|
|
result = {Curve.p3.x - Curve.p0.x, Curve.p3.z - Curve.p0.z};
|
|
return glm::length(result) > 1e-9 ? glm::normalize(result) : glm::dvec2{0.0, 1.0};
|
|
}
|
|
|
|
std::string lower(std::string Text)
|
|
{
|
|
std::transform(Text.begin(), Text.end(), Text.begin(), [](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
|
return Text;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
char const *name(kind const Kind)
|
|
{
|
|
switch (Kind)
|
|
{
|
|
case kind::model: return "model";
|
|
case kind::include: return "include";
|
|
case kind::traction: return "traction";
|
|
case kind::memcell: return "memcell";
|
|
case kind::launcher: return "eventlauncher";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
char const *name(category const Category)
|
|
{
|
|
switch (Category)
|
|
{
|
|
case category::signal: return "Signals";
|
|
case category::sign: return "Signs";
|
|
case category::pole: return "Poles and gantries";
|
|
case category::catenary: return "Catenary";
|
|
case category::logic: return "Logic";
|
|
case category::other: return "Other";
|
|
case category::equipment: return "Track equipment";
|
|
case category::environment: return "Surroundings";
|
|
}
|
|
return "";
|
|
}
|
|
|
|
std::optional<category> category_named(std::string const &Category)
|
|
{
|
|
static std::map<std::string, category> const named{{"signal", category::signal}, {"sign", category::sign}, {"pole", category::pole}, {"catenary", category::catenary}, {"equipment", category::equipment}, {"environment", category::environment},
|
|
{"switch drive", category::equipment}, {"turntable", category::equipment}, {"logic", category::logic}};
|
|
auto const found{named.find(lower(Category))};
|
|
if (found == named.end())
|
|
return std::nullopt;
|
|
return found->second;
|
|
}
|
|
|
|
std::vector<category> const &categories()
|
|
{
|
|
static std::vector<category> const result{category::signal, category::sign, category::pole, category::catenary, category::equipment, category::logic, category::environment, category::other};
|
|
return result;
|
|
}
|
|
|
|
double plan_length(segment_data const &Path)
|
|
{
|
|
return lengths(bezier{Path}).back();
|
|
}
|
|
|
|
std::vector<glm::dvec3> outline(segment_data const &Path, double const Step)
|
|
{
|
|
bezier const shape{Path};
|
|
// the speed along the curve doesn't exceed three times the longest leg of the control polygon
|
|
auto const leg{std::max({plan_distance(shape.p0, shape.p1), plan_distance(shape.p1, shape.p2), plan_distance(shape.p2, shape.p3)})};
|
|
auto const count{std::clamp(static_cast<int>(std::ceil(3.0 * leg / std::max(Step, 0.1))), 1, 4096)};
|
|
std::vector<glm::dvec3> result;
|
|
result.reserve(count + 1);
|
|
for (int i = 0; i <= count; ++i)
|
|
result.push_back(shape.point(static_cast<double>(i) / count));
|
|
return result;
|
|
}
|
|
|
|
frame frame_at(segment_data const &Path, double const S)
|
|
{
|
|
bezier const shape{Path};
|
|
auto const t{parameter(lengths(shape), S)};
|
|
return {shape.point(t), plan_direction(shape, t)};
|
|
}
|
|
|
|
frame frame_at(station const &Station)
|
|
{
|
|
if (Station.track == nullptr || Station.path < 0 || Station.path >= static_cast<int>(Station.track->m_paths.size()))
|
|
return {};
|
|
return frame_at(Station.track->m_paths[Station.path], Station.s);
|
|
}
|
|
|
|
double project(segment_data const &Path, glm::dvec3 const &Point, double &S, bool &Interior)
|
|
{
|
|
bezier const shape{Path};
|
|
auto const table{lengths(shape)};
|
|
auto const distance = [&](double const T) { return plan_distance(shape.point(T), Point); };
|
|
auto best{0};
|
|
auto bestdistance{distance(0.0)};
|
|
for (int i = 1; i <= kSteps; ++i)
|
|
{
|
|
auto const d{distance(static_cast<double>(i) / kSteps)};
|
|
if (d < bestdistance)
|
|
{
|
|
bestdistance = d;
|
|
best = i;
|
|
}
|
|
}
|
|
// golden section search around the nearest sample
|
|
auto low{std::max(0, best - 1) / static_cast<double>(kSteps)};
|
|
auto high{std::min(kSteps, best + 1) / static_cast<double>(kSteps)};
|
|
double const ratio{0.6180339887498949};
|
|
auto a{high - ratio * (high - low)};
|
|
auto b{low + ratio * (high - low)};
|
|
auto da{distance(a)}, db{distance(b)};
|
|
for (int i = 0; i < 40; ++i)
|
|
{
|
|
if (da < db)
|
|
{
|
|
high = b;
|
|
b = a;
|
|
db = da;
|
|
a = high - ratio * (high - low);
|
|
da = distance(a);
|
|
}
|
|
else
|
|
{
|
|
low = a;
|
|
a = b;
|
|
da = db;
|
|
b = low + ratio * (high - low);
|
|
db = distance(b);
|
|
}
|
|
}
|
|
auto t{(low + high) * 0.5};
|
|
if (distance(0.0) <= distance(t))
|
|
t = 0.0;
|
|
if (distance(1.0) <= distance(t))
|
|
t = 1.0;
|
|
S = length_at(table, t);
|
|
Interior = (t > 1e-6 && t < 1.0 - 1e-6);
|
|
return distance(t);
|
|
}
|
|
|
|
double project(TTrack &Track, glm::dvec3 const &Point, station &Station, bool &Interior)
|
|
{
|
|
auto best{-1.0};
|
|
for (int i = 0; i < static_cast<int>(Track.m_paths.size()); ++i)
|
|
{
|
|
double s;
|
|
bool interior;
|
|
auto const d{project(Track.m_paths[i], Point, s, interior)};
|
|
// an interior point takes precedence over an end shared with the other path
|
|
if (best < 0.0 || d < best - 1e-6 || (std::abs(d - best) <= 1e-6 && interior && false == Interior))
|
|
{
|
|
best = d;
|
|
Station = {&Track, i, s};
|
|
Interior = interior;
|
|
}
|
|
}
|
|
return best;
|
|
}
|
|
|
|
double heading(glm::dvec2 const &Forward)
|
|
{
|
|
return glm::degrees(std::atan2(Forward.x, Forward.y));
|
|
}
|
|
|
|
anchor make_anchor(TTrack *Track, int const Path, glm::dvec3 const &Point)
|
|
{
|
|
double s;
|
|
bool interior;
|
|
project(Track->m_paths[Path], Point, s, interior);
|
|
return make_anchor({Track, Path, s}, Point);
|
|
}
|
|
|
|
anchor make_anchor(station const &At, glm::dvec3 const &Point)
|
|
{
|
|
anchor result;
|
|
result.at = At;
|
|
auto const axis{frame_at(At)};
|
|
glm::dvec2 const side{axis.forward.y, -axis.forward.x};
|
|
result.offset = glm::dot(plan_of(Point - axis.point), side);
|
|
result.height = Point.y - axis.point.y;
|
|
result.foot = axis.point;
|
|
if (At.track != nullptr && At.path >= 0 && At.path < static_cast<int>(At.track->m_paths.size()))
|
|
{
|
|
auto const &path{At.track->m_paths[At.path]};
|
|
result.start = path.points[segment_data::point::start];
|
|
result.end = path.points[segment_data::point::end];
|
|
result.length = plan_length(path);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
glm::dvec3 place(anchor &Anchor)
|
|
{
|
|
auto const axis{frame_at(Anchor.at)};
|
|
Anchor.foot = axis.point;
|
|
glm::dvec2 const side{axis.forward.y, -axis.forward.x};
|
|
return {axis.point.x + side.x * Anchor.offset, axis.point.y + Anchor.height, axis.point.z + side.y * Anchor.offset};
|
|
}
|
|
|
|
std::vector<rule> default_rules()
|
|
{
|
|
return {
|
|
{category::signal, "semafor sem_ sem- tarcza tarcz_ tz_ tm_ tzw powtarz sygnaliz sygnal signal"},
|
|
{category::equipment, "equipment rezonator shp przejazd plyty zwrotni zwrl zwrp wzm -wz_ 2-5-13/ 6-9-9/ 1435mm/ podsypk most wiadukt przepust cbp- kozio przejscie szafa"},
|
|
{category::environment, "environment tree grass krzak plants/ posers/ drzew trawa las_ lawka plot_ wiata bud/ nastawni budyn lamp pojazdy/ peronowe/ kladka dworzec magazyn /dom reklama blok kwietnik"},
|
|
{category::sign, "wskaz wsk_ wsk- w4 w8 w11 w24 w27 ip/pkp/w pkp/w /we kilometr hektometr hekt slhm slupek_km znak tablic napis sign"},
|
|
{category::catenary, "trakcj siec_ siec- catenary przewod lina_nosna"},
|
|
{category::pole, "slup maszt wysieg bramk konstrukcj kotw odciag stb1 sts1 zwis tra/sb tr/-p- tr/-l- pole gantry"},
|
|
};
|
|
}
|
|
|
|
category classify(std::string const &Name, std::vector<rule> const &Rules)
|
|
{
|
|
auto const text{lower(Name)};
|
|
for (auto const &rule : Rules)
|
|
{
|
|
std::istringstream words{lower(rule.keywords)};
|
|
std::string word;
|
|
while (words >> word)
|
|
if (text.find(word) != std::string::npos)
|
|
return rule.group;
|
|
}
|
|
return category::other;
|
|
}
|
|
|
|
} // namespace infra
|