mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-31 05:20:52 +01:00
feat(editor): the kilometrage tool sets the km of the railway lines, points where the km is known and jumps of the km, the rest along the axis of the line, between the tracks of a double-track one and along the longer track where they run apart, kept in the scenery as //$k lines
The track mode Kilometrage (X, under More) puts a point of the km where a track is clicked: the line, the km and, at a jump, the km on from it. The axis of a line follows the main paths of the switches through the tracks of its points; on a double-track line it runs between the tracks, and where they part on formations of their own and come together again it runs along the longer one, the shorter one going by its own length with its last hectometre before they meet taking up the difference (PKP GK-1 6.1.12). The km between the points are stretched to the length of the scenery evenly; the panel lists the points with the stretch, the 3D view shows the axis, a tick at each hectometre, the points with their km and the km of the lines by the mouse. Read the km from the posts turns the hectometre posts along the line into the points of its km, a jump where two runs of numbers meet, and warns of a jump on a curve or at a station (Id-1 § 19). Each change is a step of undo.
This commit is contained in:
@@ -52,6 +52,7 @@ enum class icon
|
||||
turntable,
|
||||
parallel,
|
||||
station,
|
||||
kilometrage,
|
||||
more,
|
||||
// the fields of work
|
||||
work_surroundings,
|
||||
|
||||
779
editor/editorKilometrage.cpp
Normal file
779
editor/editorKilometrage.cpp
Normal file
@@ -0,0 +1,779 @@
|
||||
/*
|
||||
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/editorKilometrage.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "editor/editorTrack.hpp"
|
||||
|
||||
#include "world/Track.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "utilities/Logs.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <sstream>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace kilometrage
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using geometry::plan_distance;
|
||||
using geometry::plan_of;
|
||||
|
||||
std::string const kMark{"//$k"};
|
||||
double const kReach{60000.0}; // m, of the tracks followed each way from a mark
|
||||
double const kStep{2.0}; // m between the points of an axis
|
||||
double const kOnAxis{12.0}; // m, of a mark from the axis of its line
|
||||
double const kNear{6.0}; // m, of a mark from the track it stands on
|
||||
double const kBetweenLow{3.0}; // m between the axes of the tracks of a double-track line
|
||||
double const kBetweenHigh{6.5};
|
||||
double const kCell{10.0}; // m, of the squares the track beside is sorted into
|
||||
double const kApart{15.0}; // m between the tracks, of a double-track line on two formations
|
||||
double const kBlend{100.0}; // m, the axis takes to go over from between the tracks to one of them
|
||||
double const kLonger{1.0}; // m, of the track beside longer than the track of the line, for the axis to follow it
|
||||
|
||||
std::vector<mark> g_marks;
|
||||
std::vector<mark> g_base; // as they were before the changes not yet in the undo history
|
||||
bool g_changed{false};
|
||||
bool g_loaded{false};
|
||||
std::size_t g_edits{0};
|
||||
std::map<std::string, std::optional<axis>> g_axes;
|
||||
std::size_t g_revision{0};
|
||||
|
||||
void drop_axes()
|
||||
{
|
||||
g_axes.clear();
|
||||
++g_revision;
|
||||
}
|
||||
|
||||
using span = editor_track::route_span;
|
||||
|
||||
std::string word(std::string const &Text)
|
||||
{
|
||||
return Text.empty() ? std::string{"-"} : Text;
|
||||
}
|
||||
|
||||
// format of the lines, coordinates in the space of the file:
|
||||
// mark <line> <x y z> way <x z> km <km> [beyond <km>]
|
||||
void store(scene::layer_handle const Layer)
|
||||
{
|
||||
if (false == scene::Layers.valid(Layer))
|
||||
return;
|
||||
auto const &context{scene::Layers.layer(Layer).context_insert()};
|
||||
std::vector<std::string> lines;
|
||||
for (auto const &entry : g_marks)
|
||||
{
|
||||
if (entry.layer != Layer)
|
||||
continue;
|
||||
if (lines.empty())
|
||||
lines.emplace_back("# kilometrage of the lines set in the track editor, the simulation skips these lines");
|
||||
auto const point{context.to_local(entry.point)};
|
||||
auto const ahead{context.to_local(entry.point + glm::dvec3{entry.way.x, 0.0, entry.way.y})};
|
||||
auto way{plan_of(ahead - point)};
|
||||
way = glm::length(way) > 1e-12 ? glm::normalize(way) : entry.way;
|
||||
auto text{format("mark %s %.4f %.4f %.4f way %.9f %.9f km %.3f", word(entry.line).c_str(), point.x, point.y, point.z, way.x, way.y, entry.km)};
|
||||
if (entry.beyond.has_value())
|
||||
text += format(" beyond %.3f", *entry.beyond);
|
||||
lines.push_back(text);
|
||||
}
|
||||
scene::Layers.mark(Layer, kMark, std::move(lines));
|
||||
}
|
||||
|
||||
void changed(std::set<scene::layer_handle> const &Layers)
|
||||
{
|
||||
g_changed = true;
|
||||
drop_axes();
|
||||
for (auto const layer : Layers)
|
||||
store(layer);
|
||||
}
|
||||
|
||||
std::vector<span> flipped(std::vector<span> Spans)
|
||||
{
|
||||
std::reverse(Spans.begin(), Spans.end());
|
||||
for (auto &entry : Spans)
|
||||
entry.forward = false == entry.forward;
|
||||
return Spans;
|
||||
}
|
||||
|
||||
std::size_t index_of(std::vector<span> const &Spans, TTrack const *Track)
|
||||
{
|
||||
auto const found{std::find_if(Spans.begin(), Spans.end(), [&](span const &Entry) { return Entry.track == Track; })};
|
||||
return found == Spans.end() ? Spans.size() : static_cast<std::size_t>(found - Spans.begin());
|
||||
}
|
||||
|
||||
// the run on from the end of the span the way it goes, along the main paths of the switches
|
||||
std::vector<span> onward(span const &Last)
|
||||
{
|
||||
auto const run{editor_track::run_route(*Last.track, kReach)};
|
||||
auto const at{index_of(run.spans, Last.track)};
|
||||
if (at == run.spans.size())
|
||||
return {};
|
||||
if (run.spans[at].forward == Last.forward)
|
||||
return {run.spans.begin() + at + 1, run.spans.end()};
|
||||
return flipped({run.spans.begin(), run.spans.begin() + at});
|
||||
}
|
||||
|
||||
// the spans with each track once, the first time it comes
|
||||
std::vector<span> once(std::vector<span> const &Spans)
|
||||
{
|
||||
std::vector<span> result;
|
||||
std::unordered_set<TTrack const *> seen;
|
||||
for (auto const &entry : Spans)
|
||||
if (seen.insert(entry.track).second)
|
||||
result.push_back(entry);
|
||||
return result;
|
||||
}
|
||||
|
||||
double plan_gap(std::vector<editor_track::route_sample> const &Samples, glm::dvec3 const &Point)
|
||||
{
|
||||
auto best{std::numeric_limits<double>::max()};
|
||||
for (auto const &sample : Samples)
|
||||
best = std::min(best, plan_distance(sample.position, Point));
|
||||
return best;
|
||||
}
|
||||
|
||||
// the track beside the line: of each sample of the line the sample of it beside, where they run side by side
|
||||
struct pairing
|
||||
{
|
||||
std::vector<editor_track::route_sample> partner; // along the track beside, empty for none
|
||||
std::vector<int> match; // of each sample, -1 where the track beside isn't there
|
||||
std::vector<double> across; // to the sample beside, along the normal of the sample
|
||||
};
|
||||
|
||||
pairing pair_with(std::vector<editor_track::route_sample> const &Samples, TTrack &Beside)
|
||||
{
|
||||
pairing result;
|
||||
result.match.assign(Samples.size(), -1);
|
||||
result.across.assign(Samples.size(), 0.0);
|
||||
auto other{editor_track::run_route(Beside, kReach)};
|
||||
result.partner = editor_track::sample_route(other, kStep);
|
||||
auto const &partner{result.partner};
|
||||
auto const cell = [](glm::dvec3 const &Point) { return std::make_pair(static_cast<long>(std::floor(Point.x / kCell)), static_cast<long>(std::floor(Point.z / kCell))); };
|
||||
std::map<std::pair<long, long>, std::vector<std::size_t>> grid;
|
||||
for (std::size_t j = 0; j < partner.size(); ++j)
|
||||
grid[cell(partner[j].position)].push_back(j);
|
||||
for (std::size_t i = 0; i < Samples.size(); ++i)
|
||||
{
|
||||
auto const &sample{Samples[i]};
|
||||
glm::dvec2 const normal{-sample.direction.y, sample.direction.x};
|
||||
auto const [cx, cz]{cell(sample.position)};
|
||||
auto best{std::numeric_limits<double>::max()};
|
||||
for (auto x = cx - 1; x <= cx + 1; ++x)
|
||||
for (auto z = cz - 1; z <= cz + 1; ++z)
|
||||
{
|
||||
auto const found{grid.find({x, z})};
|
||||
if (found == grid.end())
|
||||
continue;
|
||||
for (auto const j : found->second)
|
||||
{
|
||||
auto const offset{plan_of(partner[j].position) - plan_of(sample.position)};
|
||||
auto const across{glm::dot(offset, normal)};
|
||||
auto const along{glm::dot(offset, sample.direction)};
|
||||
if (std::abs(across) < kBetweenLow || std::abs(across) > kBetweenHigh || std::abs(along) >= kStep * 1.5 || std::abs(glm::dot(sample.direction, partner[j].direction)) <= 0.97 || std::abs(along) >= best)
|
||||
continue;
|
||||
best = std::abs(along);
|
||||
result.match[i] = static_cast<int>(j);
|
||||
result.across[i] = across;
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// the track beside the line, looked for by the first mark and then every 500 m along the line: a track the line doesn't run
|
||||
// through, the spacing of a double-track line away, beside it for 100 m at least
|
||||
pairing pair_up(std::vector<editor_track::route_sample> const &Samples, editor_track::route const &Route, std::size_t const Origin)
|
||||
{
|
||||
std::unordered_set<TTrack const *> own;
|
||||
for (auto const &entry : Route.spans)
|
||||
own.insert(entry.track);
|
||||
std::unordered_set<TTrack const *> tried;
|
||||
auto const step{static_cast<std::size_t>(500.0 / kStep)};
|
||||
for (std::size_t k = 0; k * step < Samples.size() && tried.size() < 4; ++k)
|
||||
for (auto const way : {1, -1})
|
||||
{
|
||||
if (k == 0 && way < 0)
|
||||
continue;
|
||||
auto const index{static_cast<long>(Origin) + way * static_cast<long>(k * step)};
|
||||
if (index < 0 || index >= static_cast<long>(Samples.size()))
|
||||
continue;
|
||||
auto const &at{Samples[static_cast<std::size_t>(index)]};
|
||||
glm::dvec2 const left{-at.direction.y, at.direction.x};
|
||||
for (auto const side : {1.0, -1.0})
|
||||
{
|
||||
auto const probe{plan_of(at.position) + left * (side * (kBetweenLow + kBetweenHigh) * 0.5)};
|
||||
auto *track{track_near({probe.x, at.position.y, probe.y}, 1.6)};
|
||||
if (track == nullptr || own.count(track) > 0 || false == tried.insert(track).second)
|
||||
continue;
|
||||
auto result{pair_with(Samples, *track)};
|
||||
if (std::count_if(result.match.begin(), result.match.end(), [](int const Match) { return Match >= 0; }) * kStep >= 100.0)
|
||||
return result;
|
||||
}
|
||||
}
|
||||
pairing none;
|
||||
none.match.assign(Samples.size(), -1);
|
||||
none.across.assign(Samples.size(), 0.0);
|
||||
return none;
|
||||
}
|
||||
|
||||
// the axis: between the tracks where they run side by side, along the longer one where they part and come together again
|
||||
// (PKP GK-1 6.1.12), the other one getting the km by the share of its length, on the track of the line where the other one ends
|
||||
void compose(axis &Result, std::vector<editor_track::route_sample> const &Samples, pairing const &Pairs)
|
||||
{
|
||||
using sample = editor_track::route_sample;
|
||||
auto const emit = [&](glm::dvec3 const &Point, glm::dvec2 const &Direction) {
|
||||
Result.chainage.push_back(Result.points.empty() ? 0.0 : Result.chainage.back() + plan_distance(Result.points.back(), Point));
|
||||
Result.points.push_back(Point);
|
||||
Result.directions.push_back(Direction);
|
||||
};
|
||||
auto const shifted = [](glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Offset) {
|
||||
auto const planar{plan_of(Point) + glm::dvec2{-Direction.y, Direction.x} * Offset};
|
||||
return glm::dvec3{planar.x, Point.y, planar.y};
|
||||
};
|
||||
std::vector<std::size_t> matched;
|
||||
for (std::size_t i = 0; i < Samples.size(); ++i)
|
||||
if (Pairs.match[i] >= 0)
|
||||
matched.push_back(i);
|
||||
if (matched.empty())
|
||||
{
|
||||
for (auto const &entry : Samples)
|
||||
emit(entry.position, entry.direction);
|
||||
return;
|
||||
}
|
||||
Result.between = true;
|
||||
auto const half = [&](std::size_t const Index) { return Pairs.across[Index] * 0.5; };
|
||||
// from between the tracks over to one of them, within kBlend of the ends of the stretch apart
|
||||
auto const eased = [&](double const From, double const To, double const Low, double const High) {
|
||||
return From < To ? Low * (1.0 - std::clamp(From / kBlend, 0.0, 1.0)) : High * (1.0 - std::clamp(To / kBlend, 0.0, 1.0));
|
||||
};
|
||||
auto const first{matched.front()};
|
||||
for (std::size_t i = 0; i < first; ++i)
|
||||
emit(shifted(Samples[i].position, Samples[i].direction, half(first) * std::clamp(1.0 - (Samples[first].chainage - Samples[i].chainage) / kBlend, 0.0, 1.0)), Samples[i].direction);
|
||||
std::optional<axis::side> pending;
|
||||
std::vector<double> shares;
|
||||
for (std::size_t k = 0; k < matched.size(); ++k)
|
||||
{
|
||||
auto const a{matched[k]};
|
||||
emit(shifted(Samples[a].position, Samples[a].direction, half(a)), Samples[a].direction);
|
||||
if (pending)
|
||||
{
|
||||
pending->to = Result.chainage.back();
|
||||
for (auto const share : shares)
|
||||
pending->chainage.push_back(pending->from + share * (pending->to - pending->from));
|
||||
Result.sides.push_back(std::move(*pending));
|
||||
pending.reset();
|
||||
}
|
||||
if (k + 1 == matched.size())
|
||||
break;
|
||||
auto const b{matched[k + 1]};
|
||||
if (b == a + 1)
|
||||
continue;
|
||||
// the tracks apart between a and b: the track beside from the sample by a to the one by b, turned along the line
|
||||
auto const ja{Pairs.match[a]};
|
||||
auto const jb{Pairs.match[b]};
|
||||
std::vector<sample> beside;
|
||||
for (auto j = ja; j != jb; j += ja < jb ? 1 : -1)
|
||||
{
|
||||
auto entry{Pairs.partner[static_cast<std::size_t>(j)]};
|
||||
if (ja > jb)
|
||||
entry.direction = -entry.direction;
|
||||
beside.push_back(entry);
|
||||
}
|
||||
beside.push_back(Pairs.partner[static_cast<std::size_t>(jb)]);
|
||||
if (ja > jb)
|
||||
beside.back().direction = -beside.back().direction;
|
||||
std::vector<double> along{0.0};
|
||||
for (std::size_t q = 1; q < beside.size(); ++q)
|
||||
along.push_back(along.back() + plan_distance(beside[q - 1].position, beside[q].position));
|
||||
auto const own_length{Samples[b].chainage - Samples[a].chainage};
|
||||
auto const beside_length{along.back()};
|
||||
auto const at_share = [&](double const Share) {
|
||||
auto const target{Share * beside_length};
|
||||
auto const next{std::min<std::size_t>(beside.size() - 1, static_cast<std::size_t>(std::lower_bound(along.begin(), along.end(), target) - along.begin()))};
|
||||
if (next == 0)
|
||||
return beside.front().position;
|
||||
auto const span{along[next] - along[next - 1]};
|
||||
return glm::mix(beside[next - 1].position, beside[next].position, span > 1e-9 ? (target - along[next - 1]) / span : 0.0);
|
||||
};
|
||||
auto widest{0.0};
|
||||
for (auto i = a + 1; i < b; ++i)
|
||||
widest = std::max(widest, plan_distance(Samples[i].position, at_share((Samples[i].chainage - Samples[a].chainage) / own_length)));
|
||||
if (widest <= kApart || own_length < 1e-6 || beside_length < 1e-6)
|
||||
{
|
||||
// spread a little, as through a station: between them still
|
||||
for (auto i = a + 1; i < b; ++i)
|
||||
{
|
||||
auto const t{(Samples[i].chainage - Samples[a].chainage) / own_length};
|
||||
emit(shifted(Samples[i].position, Samples[i].direction, half(a) + (half(b) - half(a)) * t), Samples[i].direction);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
pending = axis::side{};
|
||||
shares.clear();
|
||||
pending->from = Result.chainage.back();
|
||||
if (beside_length > own_length + kLonger)
|
||||
{
|
||||
for (std::size_t q = 1; q + 1 < beside.size(); ++q)
|
||||
emit(shifted(beside[q].position, beside[q].direction, -eased(along[q], beside_length - along[q], half(a), half(b))), beside[q].direction);
|
||||
pending->length = own_length;
|
||||
for (auto i = a + 1; i < b; ++i)
|
||||
{
|
||||
pending->points.push_back(Samples[i].position);
|
||||
pending->along.push_back(Samples[i].chainage - Samples[a].chainage);
|
||||
shares.push_back(pending->along.back() / own_length);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (auto i = a + 1; i < b; ++i)
|
||||
emit(shifted(Samples[i].position, Samples[i].direction, eased(Samples[i].chainage - Samples[a].chainage, Samples[b].chainage - Samples[i].chainage, half(a), half(b))), Samples[i].direction);
|
||||
pending->length = beside_length;
|
||||
for (std::size_t q = 1; q + 1 < beside.size(); ++q)
|
||||
{
|
||||
pending->points.push_back(beside[q].position);
|
||||
pending->along.push_back(along[q]);
|
||||
shares.push_back(along[q] / beside_length);
|
||||
}
|
||||
}
|
||||
}
|
||||
auto const last{matched.back()};
|
||||
for (auto i = last + 1; i < Samples.size(); ++i)
|
||||
emit(shifted(Samples[i].position, Samples[i].direction, half(last) * std::clamp(1.0 - (Samples[i].chainage - Samples[last].chainage) / kBlend, 0.0, 1.0)), Samples[i].direction);
|
||||
}
|
||||
|
||||
std::optional<axis> build(std::string const &Line)
|
||||
{
|
||||
std::vector<std::size_t> own;
|
||||
for (std::size_t index = 0; index < g_marks.size(); ++index)
|
||||
if (g_marks[index].line == Line)
|
||||
own.push_back(index);
|
||||
if (own.empty())
|
||||
return std::nullopt;
|
||||
std::vector<TTrack *> tracks;
|
||||
for (auto const index : own)
|
||||
tracks.push_back(track_near(g_marks[index].point, kNear));
|
||||
auto const start{std::find_if(tracks.begin(), tracks.end(), [](TTrack const *Track) { return Track != nullptr; })};
|
||||
if (start == tracks.end())
|
||||
return std::nullopt;
|
||||
auto *first{*start};
|
||||
auto const first_mark{own[static_cast<std::size_t>(start - tracks.begin())]};
|
||||
auto route{editor_track::run_route(*first, kReach)};
|
||||
if (route.spans.empty())
|
||||
return std::nullopt;
|
||||
// the marks the main paths of the switches lead away from: the line goes their way
|
||||
std::set<std::size_t> tried;
|
||||
std::vector<editor_track::route_sample> samples;
|
||||
pairing pairs;
|
||||
auto const origin_of = [&](std::vector<editor_track::route_sample> const &Samples) {
|
||||
std::size_t origin{0};
|
||||
auto nearest{std::numeric_limits<double>::max()};
|
||||
for (std::size_t i = 0; i < Samples.size(); ++i)
|
||||
if (auto const distance{plan_distance(Samples[i].position, g_marks[first_mark].point)}; distance < nearest)
|
||||
{
|
||||
nearest = distance;
|
||||
origin = i;
|
||||
}
|
||||
return origin;
|
||||
};
|
||||
for (std::size_t round = 0; round <= own.size(); ++round)
|
||||
{
|
||||
samples = editor_track::sample_route(route, kStep);
|
||||
pairs = pair_up(samples, route, origin_of(samples));
|
||||
std::size_t away{own.size()};
|
||||
for (std::size_t k = 0; k < own.size() && away == own.size(); ++k)
|
||||
if (tracks[k] != nullptr && tried.count(k) == 0 && plan_gap(samples, g_marks[own[k]].point) > kOnAxis && plan_gap(pairs.partner, g_marks[own[k]].point) > kOnAxis)
|
||||
away = k;
|
||||
if (away == own.size())
|
||||
break;
|
||||
tried.insert(away);
|
||||
editor_track::route found;
|
||||
std::string error;
|
||||
if (false == editor_track::find_route(first, tracks[away], found, error) || found.spans.size() < 2)
|
||||
continue;
|
||||
auto const at{index_of(route.spans, first)};
|
||||
if (at == route.spans.size())
|
||||
break;
|
||||
std::vector<span> spans;
|
||||
if (found.spans.front().forward == route.spans[at].forward)
|
||||
{
|
||||
spans.assign(route.spans.begin(), route.spans.begin() + at);
|
||||
spans.insert(spans.end(), found.spans.begin(), found.spans.end());
|
||||
auto const more{onward(found.spans.back())};
|
||||
spans.insert(spans.end(), more.begin(), more.end());
|
||||
}
|
||||
else
|
||||
{
|
||||
spans = flipped(onward(found.spans.back()));
|
||||
auto const back{flipped(found.spans)};
|
||||
spans.insert(spans.end(), back.begin(), back.end());
|
||||
spans.insert(spans.end(), route.spans.begin() + at + 1, route.spans.end());
|
||||
}
|
||||
route.spans = once(spans);
|
||||
}
|
||||
if (samples.size() < 2)
|
||||
return std::nullopt;
|
||||
axis result;
|
||||
result.line = Line;
|
||||
compose(result, samples, pairs);
|
||||
for (auto const index : own)
|
||||
{
|
||||
axis::anchor anchor;
|
||||
anchor.mark = index;
|
||||
anchor.chainage = chainage_at(result, g_marks[index].point, anchor.gap);
|
||||
if (anchor.gap > kOnAxis)
|
||||
{
|
||||
result.astray.push_back(index);
|
||||
continue;
|
||||
}
|
||||
anchor.before = g_marks[index].km;
|
||||
anchor.after = g_marks[index].beyond.value_or(g_marks[index].km);
|
||||
result.anchors.push_back(anchor);
|
||||
}
|
||||
if (result.anchors.empty())
|
||||
return std::nullopt;
|
||||
std::sort(result.anchors.begin(), result.anchors.end(), [](axis::anchor const &A, axis::anchor const &B) { return A.chainage < B.chainage; });
|
||||
// the km rise the way the marks grow, the way the only one says
|
||||
auto const &low{result.anchors.front()};
|
||||
auto const &high{result.anchors.back()};
|
||||
if (result.anchors.size() > 1 && std::abs(high.before - low.before) > 1e-6)
|
||||
result.growth = high.before > low.before ? 1 : -1;
|
||||
else
|
||||
result.growth = glm::dot(g_marks[low.mark].way, direction_on(result, low.chainage)) >= 0.0 ? 1 : -1;
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void reset()
|
||||
{
|
||||
g_marks.clear();
|
||||
g_base.clear();
|
||||
drop_axes();
|
||||
g_changed = false;
|
||||
g_loaded = false;
|
||||
}
|
||||
|
||||
void load()
|
||||
{
|
||||
if (g_loaded || scene::Layers.empty())
|
||||
return;
|
||||
g_loaded = true;
|
||||
g_marks.clear();
|
||||
drop_axes();
|
||||
for (auto const layer : scene::Layers.marked_layers(kMark))
|
||||
{
|
||||
auto const &context{scene::Layers.layer(layer).context_insert()};
|
||||
for (auto const &text : scene::Layers.marked(layer, kMark))
|
||||
{
|
||||
std::istringstream line{text};
|
||||
std::string keyword;
|
||||
mark entry;
|
||||
entry.layer = layer;
|
||||
glm::dvec3 point{0.0};
|
||||
glm::dvec2 way{0.0, 1.0};
|
||||
line >> keyword;
|
||||
if (keyword != "mark")
|
||||
continue;
|
||||
line >> entry.line >> point.x >> point.y >> point.z >> keyword >> way.x >> way.y >> keyword >> entry.km;
|
||||
if (line.fail() || entry.line.empty() || false == std::isfinite(entry.km))
|
||||
continue;
|
||||
while (line >> keyword)
|
||||
if (keyword == "beyond")
|
||||
{
|
||||
double beyond{0.0};
|
||||
if (line >> beyond)
|
||||
entry.beyond = beyond;
|
||||
}
|
||||
entry.point = context.to_world(point);
|
||||
entry.way = plan_of(context.to_world(point + glm::dvec3{way.x, 0.0, way.y}) - entry.point);
|
||||
entry.way = glm::length(entry.way) > 1e-12 ? glm::normalize(entry.way) : glm::dvec2{0.0, 1.0};
|
||||
g_marks.push_back(std::move(entry));
|
||||
}
|
||||
}
|
||||
g_base = g_marks;
|
||||
g_changed = false;
|
||||
if (false == g_marks.empty())
|
||||
WriteLog("Editor: " + std::to_string(g_marks.size()) + " kilometrage mark(s) read from the scenery", logtype::generic);
|
||||
}
|
||||
|
||||
std::vector<mark> const &marks()
|
||||
{
|
||||
load();
|
||||
return g_marks;
|
||||
}
|
||||
|
||||
void put(mark Entry)
|
||||
{
|
||||
load();
|
||||
auto *track{track_near(Entry.point, kNear)};
|
||||
Entry.layer = track != nullptr ? alignment_store::layer_of(*track) : scene::Layers.active();
|
||||
if (false == scene::Layers.valid(Entry.layer))
|
||||
Entry.layer = 1;
|
||||
auto const layer{Entry.layer};
|
||||
g_marks.push_back(std::move(Entry));
|
||||
changed({layer});
|
||||
}
|
||||
|
||||
void change(std::size_t const Index, mark Entry)
|
||||
{
|
||||
load();
|
||||
if (Index >= g_marks.size())
|
||||
return;
|
||||
Entry.layer = g_marks[Index].layer;
|
||||
g_marks[Index] = std::move(Entry);
|
||||
changed({g_marks[Index].layer});
|
||||
}
|
||||
|
||||
void remove(std::size_t const Index)
|
||||
{
|
||||
load();
|
||||
if (Index >= g_marks.size())
|
||||
return;
|
||||
auto const layer{g_marks[Index].layer};
|
||||
g_marks.erase(g_marks.begin() + static_cast<std::ptrdiff_t>(Index));
|
||||
changed({layer});
|
||||
}
|
||||
|
||||
std::optional<std::vector<mark>> take_changes()
|
||||
{
|
||||
if (false == g_changed)
|
||||
return std::nullopt;
|
||||
auto before{std::move(g_base)};
|
||||
g_base = g_marks;
|
||||
g_changed = false;
|
||||
return before;
|
||||
}
|
||||
|
||||
void settle()
|
||||
{
|
||||
g_base = g_marks;
|
||||
g_changed = false;
|
||||
}
|
||||
|
||||
void restore(std::vector<mark> Marks)
|
||||
{
|
||||
std::set<scene::layer_handle> layers;
|
||||
for (auto const *list : {&g_marks, &Marks})
|
||||
for (auto const &entry : *list)
|
||||
layers.insert(entry.layer);
|
||||
g_marks = std::move(Marks);
|
||||
changed(layers);
|
||||
settle();
|
||||
}
|
||||
|
||||
void follow(std::size_t const Edits)
|
||||
{
|
||||
if (Edits == g_edits)
|
||||
return;
|
||||
g_edits = Edits;
|
||||
drop_axes();
|
||||
}
|
||||
|
||||
std::size_t revision()
|
||||
{
|
||||
return g_revision;
|
||||
}
|
||||
|
||||
std::vector<std::string> lines()
|
||||
{
|
||||
load();
|
||||
std::vector<std::string> result;
|
||||
for (auto const &entry : g_marks)
|
||||
if (std::find(result.begin(), result.end(), entry.line) == result.end())
|
||||
result.push_back(entry.line);
|
||||
std::sort(result.begin(), result.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
axis const *axis_of(std::string const &Line)
|
||||
{
|
||||
load();
|
||||
auto found{g_axes.find(Line)};
|
||||
if (found == g_axes.end())
|
||||
found = g_axes.emplace(Line, build(Line)).first;
|
||||
return found->second.has_value() ? &*found->second : nullptr;
|
||||
}
|
||||
|
||||
double km_on(axis const &Axis, double const Chainage)
|
||||
{
|
||||
auto const &anchors{Axis.anchors};
|
||||
if (anchors.empty())
|
||||
return 0.0;
|
||||
auto const growth{static_cast<double>(Axis.growth)};
|
||||
// the km of an anchor on the side of the lower chainage, and of the higher one
|
||||
auto const lower = [&](axis::anchor const &Anchor) { return Axis.growth > 0 ? Anchor.before : Anchor.after; };
|
||||
auto const higher = [&](axis::anchor const &Anchor) { return Axis.growth > 0 ? Anchor.after : Anchor.before; };
|
||||
if (Chainage <= anchors.front().chainage)
|
||||
return lower(anchors.front()) - growth * (anchors.front().chainage - Chainage) / 1000.0;
|
||||
if (Chainage >= anchors.back().chainage)
|
||||
return higher(anchors.back()) + growth * (Chainage - anchors.back().chainage) / 1000.0;
|
||||
auto const next{std::upper_bound(anchors.begin(), anchors.end(), Chainage, [](double const Value, axis::anchor const &Anchor) { return Value < Anchor.chainage; })};
|
||||
auto const &to{*next};
|
||||
auto const &from{*std::prev(next)};
|
||||
auto const span{to.chainage - from.chainage};
|
||||
if (span < 1e-6)
|
||||
return higher(from);
|
||||
return higher(from) + (lower(to) - higher(from)) * (Chainage - from.chainage) / span;
|
||||
}
|
||||
|
||||
double km_on(axis const &Axis, axis::side const &Side, double const Along)
|
||||
{
|
||||
auto const low{km_on(Axis, Side.from + 1e-6)};
|
||||
auto const high{km_on(Axis, Side.to - 1e-6)};
|
||||
auto const length{Side.length};
|
||||
if (length < 1e-6 || Side.to - Side.from < 1e-6)
|
||||
return low;
|
||||
// along the rising km: from the km the stretch starts at, the irregular hectometre before the one it ends at
|
||||
auto const rising{Axis.growth > 0};
|
||||
auto const start{rising ? low : high};
|
||||
auto const end{rising ? high : low};
|
||||
auto const s{std::clamp(rising ? Along : length - Along, 0.0, length)};
|
||||
auto const scale{std::abs(end - start) / ((Side.to - Side.from) / 1000.0)}; // km of the axis to a km of the scenery
|
||||
auto const regular{start + scale * length / 1000.0};
|
||||
auto const last{(std::ceil(std::min(regular, end) * 10.0 - 1e-9) - 1.0) / 10.0};
|
||||
if (end <= start || last <= start || scale <= 1e-9)
|
||||
return start + (end - start) * s / length;
|
||||
auto const at{(last - start) / scale * 1000.0};
|
||||
if (s <= at || at >= length)
|
||||
return start + scale * s / 1000.0;
|
||||
return last + (end - last) * (s - at) / (length - at);
|
||||
}
|
||||
|
||||
double chainage_of(axis const &Axis, glm::dvec3 const &Point, double &Gap)
|
||||
{
|
||||
Gap = std::numeric_limits<double>::max();
|
||||
auto result{0.0};
|
||||
auto const point{plan_of(Point)};
|
||||
for (std::size_t i = 1; i < Axis.points.size(); ++i)
|
||||
{
|
||||
auto const a{plan_of(Axis.points[i - 1])};
|
||||
auto const along{plan_of(Axis.points[i]) - a};
|
||||
auto const length2{glm::dot(along, along)};
|
||||
auto const t{length2 > 1e-12 ? std::clamp(glm::dot(point - a, along) / length2, 0.0, 1.0) : 0.0};
|
||||
if (auto const gap{glm::distance(point, a + along * t)}; gap < Gap)
|
||||
{
|
||||
Gap = gap;
|
||||
result = Axis.chainage[i - 1] + (Axis.chainage[i] - Axis.chainage[i - 1]) * t;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double chainage_at(axis const &Axis, glm::dvec3 const &Point, double &Gap)
|
||||
{
|
||||
auto result{chainage_of(Axis, Point, Gap)};
|
||||
auto const point{plan_of(Point)};
|
||||
for (auto const &entry : Axis.sides)
|
||||
for (std::size_t i = 1; i < entry.points.size(); ++i)
|
||||
{
|
||||
auto const a{plan_of(entry.points[i - 1])};
|
||||
auto const along{plan_of(entry.points[i]) - a};
|
||||
auto const length2{glm::dot(along, along)};
|
||||
auto const t{length2 > 1e-12 ? std::clamp(glm::dot(point - a, along) / length2, 0.0, 1.0) : 0.0};
|
||||
if (auto const gap{glm::distance(point, a + along * t)}; gap < Gap)
|
||||
{
|
||||
Gap = gap;
|
||||
result = entry.chainage[i - 1] + (entry.chainage[i] - entry.chainage[i - 1]) * t;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
glm::dvec3 point_on(axis const &Axis, double const Chainage)
|
||||
{
|
||||
if (Axis.points.empty())
|
||||
return glm::dvec3{0.0};
|
||||
auto const after{std::lower_bound(Axis.chainage.begin(), Axis.chainage.end(), Chainage)};
|
||||
if (after == Axis.chainage.begin())
|
||||
return Axis.points.front();
|
||||
if (after == Axis.chainage.end())
|
||||
return Axis.points.back();
|
||||
auto const i{static_cast<std::size_t>(after - Axis.chainage.begin())};
|
||||
auto const step{Axis.chainage[i] - Axis.chainage[i - 1]};
|
||||
return glm::mix(Axis.points[i - 1], Axis.points[i], step > 1e-9 ? (Chainage - Axis.chainage[i - 1]) / step : 0.0);
|
||||
}
|
||||
|
||||
glm::dvec2 direction_on(axis const &Axis, double const Chainage)
|
||||
{
|
||||
if (Axis.directions.empty())
|
||||
return {0.0, 1.0};
|
||||
auto const after{std::lower_bound(Axis.chainage.begin(), Axis.chainage.end(), Chainage)};
|
||||
auto const i{std::min(Axis.directions.size() - 1, static_cast<std::size_t>(after - Axis.chainage.begin()))};
|
||||
return Axis.directions[i];
|
||||
}
|
||||
|
||||
bool km_at(std::string const &Line, glm::dvec3 const &Point, double &Km, double const Reach)
|
||||
{
|
||||
auto const *found{axis_of(Line)};
|
||||
if (found == nullptr)
|
||||
return false;
|
||||
double gap{0.0};
|
||||
auto const chainage{chainage_of(*found, Point, gap)};
|
||||
auto const point{plan_of(Point)};
|
||||
axis::side const *apart{nullptr};
|
||||
auto along{0.0};
|
||||
for (auto const &entry : found->sides)
|
||||
for (std::size_t i = 1; i < entry.points.size(); ++i)
|
||||
{
|
||||
auto const a{plan_of(entry.points[i - 1])};
|
||||
auto const ab{plan_of(entry.points[i]) - a};
|
||||
auto const length2{glm::dot(ab, ab)};
|
||||
auto const t{length2 > 1e-12 ? std::clamp(glm::dot(point - a, ab) / length2, 0.0, 1.0) : 0.0};
|
||||
if (auto const distance{glm::distance(point, a + ab * t)}; distance < gap)
|
||||
{
|
||||
gap = distance;
|
||||
apart = &entry;
|
||||
along = entry.along[i - 1] + (entry.along[i] - entry.along[i - 1]) * t;
|
||||
}
|
||||
}
|
||||
if (gap > Reach)
|
||||
return false;
|
||||
Km = apart != nullptr ? km_on(*found, *apart, along) : km_on(*found, chainage);
|
||||
return true;
|
||||
}
|
||||
|
||||
TTrack *track_near(glm::dvec3 const &Point, double const Reach)
|
||||
{
|
||||
TTrack *best{nullptr};
|
||||
auto nearest{Reach};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || (track->iCategoryFlag & 1) == 0 || false == editor_track::is_supported(*track) || track->m_paths.empty())
|
||||
continue;
|
||||
auto const &ends{track->m_paths.front().points};
|
||||
auto const chord{plan_distance(ends[segment_data::point::start], ends[segment_data::point::end])};
|
||||
if (std::min(plan_distance(ends[segment_data::point::start], Point), plan_distance(ends[segment_data::point::end], Point)) > chord * 0.8 + nearest + 1.0)
|
||||
continue;
|
||||
auto const on{editor_track::point_at(*track, editor_track::nearest_parameter(*track, Point))};
|
||||
if (auto const distance{plan_distance(on, Point)}; distance < nearest)
|
||||
{
|
||||
nearest = distance;
|
||||
best = track;
|
||||
}
|
||||
}
|
||||
return best;
|
||||
}
|
||||
|
||||
std::string text(double const Km)
|
||||
{
|
||||
auto result{format("%.3f", Km)};
|
||||
std::replace(result.begin(), result.end(), '.', ',');
|
||||
return result;
|
||||
}
|
||||
|
||||
} // namespace kilometrage
|
||||
111
editor/editorKilometrage.hpp
Normal file
111
editor/editorKilometrage.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
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
|
||||
|
||||
#include <cstddef>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "scene/scenelayers.h"
|
||||
|
||||
class TTrack;
|
||||
|
||||
// the kilometrage of the railway lines, set in the track editor and kept in the scenery files as "//$k" comment lines: the
|
||||
// points of a line where its km is known, from a post or a document, and the points where it jumps, with the km on both
|
||||
// sides. the km run along the axis of a single-track line and between the tracks of a double-track one, along the longer track
|
||||
// where the tracks part on formations of their own (PKP GK-1 6.1.12), the other tracks get the km straight across from them
|
||||
namespace kilometrage
|
||||
{
|
||||
|
||||
struct mark
|
||||
{
|
||||
scene::layer_handle layer{null_handle};
|
||||
std::string line; // the number of the line, as 91
|
||||
glm::dvec3 point{0.0}; // on the axis of a track of the line
|
||||
glm::dvec2 way{0.0, 1.0}; // the km rise this way along the line there, in the plan
|
||||
double km{0.0}; // the km the point is reached at, coming along the rising km
|
||||
std::optional<double> beyond; // of a jump: the km on from the point
|
||||
};
|
||||
|
||||
void reset();
|
||||
void load();
|
||||
std::vector<mark> const &marks();
|
||||
// a new mark, kept in the file of the layer of the track nearest to it
|
||||
void put(mark Entry);
|
||||
void change(std::size_t const Index, mark Entry);
|
||||
void remove(std::size_t const Index);
|
||||
// the marks as they were before the changes made since the last call, if there were any; the current ones become the base
|
||||
std::optional<std::vector<mark>> take_changes();
|
||||
// the current marks become the base, the changes made so far aren't a part of the undo history
|
||||
void settle();
|
||||
// puts the marks back as they were, for undo and redo
|
||||
void restore(std::vector<mark> Marks);
|
||||
// the axes found are dropped when the tracks change: Edits counts the changes of the tracks made in the editor
|
||||
void follow(std::size_t const Edits);
|
||||
// counts the times the axes were dropped: what is made from them is made again when it changes
|
||||
std::size_t revision();
|
||||
|
||||
// the axis a line is measured along
|
||||
struct axis
|
||||
{
|
||||
std::string line;
|
||||
bool between{false}; // between the tracks of a double-track line, else along the track of the line
|
||||
std::vector<glm::dvec3> points; // a couple of metres apart
|
||||
std::vector<glm::dvec2> directions; // of the points, in the plan
|
||||
std::vector<double> chainage; // of the points, along them
|
||||
int growth{1}; // the km rise along the chainage, -1: against it
|
||||
struct anchor
|
||||
{
|
||||
std::size_t mark{0}; // of the marks
|
||||
double chainage{0.0};
|
||||
double gap{0.0}; // m from the mark to the axis, in the plan
|
||||
double before{0.0}; // the km the point is reached at, coming along the rising km
|
||||
double after{0.0}; // the km on from it, other than before at a jump
|
||||
};
|
||||
// the shorter track of a double-track line where the tracks run on formations of their own, the axis along the longer one: its
|
||||
// km go by its own length from the end of the stretch the km rise from, the irregular hectometre at the other end takes up the
|
||||
// difference (PKP GK-1 6.1.12)
|
||||
struct side
|
||||
{
|
||||
std::vector<glm::dvec3> points;
|
||||
std::vector<double> along; // m from the start of the stretch, along the track
|
||||
std::vector<double> chainage; // of the axis, by the share of the length: where a mark on the track anchors
|
||||
double length{0.0}; // of the track between the ends of the stretch
|
||||
double from{0.0}; // the chainage of the ends of the stretch
|
||||
double to{0.0};
|
||||
};
|
||||
std::vector<side> sides;
|
||||
std::vector<anchor> anchors; // by the chainage
|
||||
std::vector<std::size_t> astray; // the marks of the line away from its axis
|
||||
double length() const { return chainage.empty() ? 0.0 : chainage.back(); }
|
||||
};
|
||||
// the numbers of the lines with marks
|
||||
std::vector<std::string> lines();
|
||||
// the axis of the line through the tracks of its marks, nullptr while it has none; kept until the marks or the tracks change
|
||||
axis const *axis_of(std::string const &Line);
|
||||
double km_on(axis const &Axis, double const Chainage);
|
||||
// where the point lies along the axis, Gap: how far from it in the plan
|
||||
double chainage_of(axis const &Axis, glm::dvec3 const &Point, double &Gap);
|
||||
// the same through the tracks running apart from the axis too, where they are nearer: the chainage the point takes the km of
|
||||
double chainage_at(axis const &Axis, glm::dvec3 const &Point, double &Gap);
|
||||
glm::dvec3 point_on(axis const &Axis, double const Chainage);
|
||||
glm::dvec2 direction_on(axis const &Axis, double const Chainage);
|
||||
// the km of a track running apart from the axis, Along: m from the start of the stretch
|
||||
double km_on(axis const &Axis, axis::side const &Side, double const Along);
|
||||
// the km of the line at the point, when it lies within the reach of the axis
|
||||
bool km_at(std::string const &Line, glm::dvec3 const &Point, double &Km, double const Reach = 30.0);
|
||||
// the track nearest to the point in the plan within the reach, nullptr for none
|
||||
TTrack *track_near(glm::dvec3 const &Point, double const Reach);
|
||||
// "100,234", as PKP PLK write the km
|
||||
std::string text(double const Km);
|
||||
|
||||
} // namespace kilometrage
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB |
@@ -340,6 +340,16 @@ def station(d):
|
||||
line(d, [(46, 36), (46, 56)], W - 10)
|
||||
|
||||
|
||||
def kilometrage(d):
|
||||
# a hectometre post by the track with its digits, and the way the km rise along the track
|
||||
rect(d, 16, 4, 36, 40, outline=True, width=W - 6, radius=3)
|
||||
line(d, [(22, 15), (30, 15)], W - 8)
|
||||
line(d, [(22, 28), (30, 28)], W - 8)
|
||||
line(d, [(26, 40), (26, 48)], W - 6)
|
||||
line(d, [(4, 56), (48, 56)], W - 6)
|
||||
arrowhead(d, 54, 56, 0, 12)
|
||||
|
||||
|
||||
def more(d):
|
||||
for x in (12, 32, 52):
|
||||
circle(d, x, 32, 6)
|
||||
@@ -390,7 +400,7 @@ def work_terrain(d):
|
||||
ICONS = [select, insert, brush, area_fill, copy_to_bank, translate, rotate, scale,
|
||||
local_space, ortho, perspective, sculpt, smooth, chunks, orthophoto, road,
|
||||
place, lanes, lay_track, switch, straight, curve, signal, objects,
|
||||
vehicle, profile, speed, joints, infra, gauge, turntable, parallel, station, more, work_surroundings,
|
||||
vehicle, profile, speed, joints, infra, gauge, turntable, parallel, station, kilometrage, more, work_surroundings,
|
||||
work_tracks, work_roads, work_terrain]
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user