17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-18 04:29:18 +02:00

feat(tracks): switches keep their neighbours joined on replacing, flipping and moving; events copied on splitting; circuits, overhead line, sleepers and events of the paths; curve design through the switches of a line; changes in files included more than once or with parameters reported instead of written; split of a straight at the point found, not a share of its length

This commit is contained in:
maj00r
2026-10-05 01:21:11 +02:00
parent 8f050d9c3e
commit b07050fa52
5 changed files with 567 additions and 40 deletions

View File

@@ -269,6 +269,11 @@ result fit_between(design const &Design)
auto const start{plan_of(Design.start)};
auto const end{plan_of(Design.end)};
if (glm::length(Design.start_direction) < 1e-9 || glm::length(Design.end_direction) < 1e-9)
{
r.errors.emplace_back("The direction of an end of the fragment is undefined, its end path has zero length");
return r;
}
auto const startdirection{glm::normalize(Design.start_direction)};
auto const enddirection{glm::normalize(Design.end_direction)};
auto const count{Design.vertices.size()};
@@ -872,7 +877,8 @@ std::size_t minimum_pieces(result const &Result, design const &Design)
return count;
}
std::vector<segment_data> pieces(result const &Result, design const &Design, std::size_t const Count)
std::vector<segment_data> pieces(result const &Result, design const &Design, std::size_t const Count, std::vector<double> const &Breaks, std::vector<std::size_t> const &Counts,
std::vector<std::size_t> *Intervals)
{
struct span
{
@@ -920,23 +926,56 @@ std::vector<segment_data> pieces(result const &Result, design const &Design, std
}
spans.erase(spans.begin() + i);
}
while (false == spans.empty() && spans.size() < Count)
{
auto longest = std::max_element(spans.begin(), spans.end(), [&](span const &A, span const &B) { return extent(A) < extent(B); });
span second{*longest};
if (longest->generic)
// splits the span at the chainage
auto const split = [&](std::vector<span>::iterator Span, double const Chainage) {
span second{*Span};
if (Span->generic)
{
auto const middle{(longest->start + longest->end) * 0.5};
second.start = middle;
longest->end = middle;
second.start = Chainage;
Span->end = Chainage;
}
else
{
auto const middle{(longest->from + longest->to) * 0.5};
second.from = middle;
longest->to = middle;
auto const local{Chainage - Result.elements[Span->element].chainage};
second.from = local;
Span->to = local;
}
spans.insert(std::next(longest), second);
spans.insert(std::next(Span), second);
};
double constexpr margin{0.01};
for (auto const chainage : Breaks)
{
auto inside{std::find_if(spans.begin(), spans.end(), [&](span const &Span) { return chainage_from(Span) < chainage - margin && chainage_to(Span) > chainage + margin; })};
if (inside != spans.end())
split(inside, chainage);
}
auto const interval = [&](span const &Span) {
return static_cast<std::size_t>(std::upper_bound(Breaks.begin(), Breaks.end(), (chainage_from(Span) + chainage_to(Span)) * 0.5) - Breaks.begin());
};
for (std::size_t k = 0; k < Counts.size(); ++k)
{
while (true)
{
auto const inside{static_cast<std::size_t>(std::count_if(spans.begin(), spans.end(), [&](span const &Span) { return interval(Span) == k; }))};
if (inside == 0 || inside >= Counts[k])
break;
auto longest{spans.end()};
for (auto candidate = spans.begin(); candidate != spans.end(); ++candidate)
if (interval(*candidate) == k && (longest == spans.end() || extent(*candidate) > extent(*longest)))
longest = candidate;
split(longest, (chainage_from(*longest) + chainage_to(*longest)) * 0.5);
}
}
while (false == spans.empty() && spans.size() < Count)
{
auto longest = std::max_element(spans.begin(), spans.end(), [&](span const &A, span const &B) { return extent(A) < extent(B); });
split(longest, (chainage_from(*longest) + chainage_to(*longest)) * 0.5);
}
if (Intervals != nullptr)
{
Intervals->clear();
for (auto const &piece : spans)
Intervals->push_back(interval(piece));
}
std::vector<segment_data> result;

View File

@@ -173,6 +173,9 @@ double elevation(result const &Result, double const Chainage);
double grade(result const &Result, double const Chainage);
std::size_t minimum_pieces(result const &Result, design const &Design);
std::vector<segment_data> pieces(result const &Result, design const &Design, std::size_t const Count);
// Breaks: ascending chainages at which pieces have to end. Counts: least number of the pieces between the consecutive breaks,
// one more than the breaks; Intervals receives the number of the interval of each piece
std::vector<segment_data> pieces(result const &Result, design const &Design, std::size_t const Count, std::vector<double> const &Breaks = {}, std::vector<std::size_t> const &Counts = {},
std::vector<std::size_t> *Intervals = nullptr);
}

View File

@@ -15,6 +15,7 @@ http:
#include "world/Track.h"
#include "vehicle/DynObj.h"
#include "scene/scene.h"
#include "scene/scenelayers.h"
#include "simulation/simulation.h"
#include "rendering/renderer.h"
#include "utilities/Globals.h"
@@ -145,9 +146,82 @@ editor_track::state editor_track::capture(TTrack const &Track)
result.texheight = texture_height(Track);
result.texwidth = Track.fTexWidth;
result.texslope = Track.fTexSlope;
for (auto const *isolated : Track.Isolated)
result.isolated.push_back(isolated->asName);
result.overhead = Track.fOverhead;
result.sleepers = Track.m_sleeper_enabled;
result.sleeper_frequency = Track.m_sleeper_frequency;
result.sleeper_model = Track.m_sleeper_model_name;
result.sleeper_skin = Track.m_sleeper_skin_name;
result.sleeper_offset = Track.m_sleeper_offset;
result.sleeper_ballast = Track.m_sleeper_ballast_z;
for (int i = 0; i < 6; ++i)
result.events[i] = events(const_cast<TTrack &>(Track), i);
return result;
}
std::vector<std::pair<std::string, basic_event *>> &editor_track::events(TTrack &Track, int const Index)
{
switch (Index)
{
case 1: return Track.m_events1;
case 2: return Track.m_events2;
case 3: return Track.m_events0all;
case 4: return Track.m_events1all;
case 5: return Track.m_events2all;
default: return Track.m_events0;
}
}
char const *editor_track::event_keyword(int const Index)
{
char const *const keywords[] = {"event0", "event1", "event2", "eventall0", "eventall1", "eventall2"};
return keywords[std::clamp(Index, 0, 5)];
}
void editor_track::bind_events(TTrack &Track, std::string &Missing)
{
Missing.clear();
Track.m_events = false;
for (int i = 0; i < 6; ++i)
for (auto &event : events(Track, i))
{
// the ones bound by the name of the path have no name of their own
if (false == event.first.empty())
event.second = simulation::Events.FindEvent(event.first);
if (event.second != nullptr)
Track.m_events = true;
else
Missing += (Missing.empty() ? "" : ", ") + event.first;
}
}
void editor_track::isolated(TTrack &Track, std::vector<std::string> const &Names)
{
Track.Isolated.clear();
for (auto const &name : Names)
{
if (name.empty())
continue;
bool known{false};
for (auto *existing{TIsolated::Root()}; existing != nullptr && false == known; existing = existing->Next())
known = (existing->asName == name);
auto *isolated{TIsolated::Find(name)};
if (false == known)
isolated->AssignEvents();
Track.Isolated.push_back(isolated);
}
}
void editor_track::overhead(TTrack &Track, float const Overhead)
{
Track.fOverhead = Overhead;
if (Overhead > 0.f)
Track.iAction |= 0x40;
else
Track.iAction &= ~0x40;
}
void editor_track::apply(TTrack &Track, state const &State)
{
Track.m_paths = State.paths;
@@ -170,6 +244,19 @@ void editor_track::apply(TTrack &Track, state const &State)
texture_height(Track, State.texheight);
Track.fTexWidth = State.texwidth;
Track.fTexSlope = State.texslope;
isolated(Track, State.isolated);
overhead(Track, State.overhead);
Track.m_sleeper_enabled = State.sleepers;
Track.m_sleeper_frequency = State.sleeper_frequency;
Track.m_sleeper_model_name = State.sleeper_model;
Track.m_sleeper_skin_name = State.sleeper_skin;
Track.m_sleeper_offset = State.sleeper_offset;
Track.m_sleeper_ballast_z = State.sleeper_ballast;
for (int i = 0; i < 6; ++i)
events(Track, i) = State.events[i];
Track.m_events = std::any_of(State.events.begin(), State.events.end(), [](auto const &Events) {
return std::any_of(Events.begin(), Events.end(), [](auto const &Event) { return Event.second != nullptr; });
});
}
bool editor_track::is_supported(TTrack const &Track)
@@ -198,6 +285,12 @@ bool editor_track::can_edit_geometry(TTrack const &Track, std::string &Reason)
Reason = "Path geometry can't be rebuilt with the experimental renderer";
return false;
}
std::string why;
if (false == scene::Layers.editable(&Track, &why))
{
Reason = "The path can't be changed: " + why;
return false;
}
Reason.clear();
return true;
}
@@ -387,6 +480,62 @@ std::vector<std::pair<TTrack *, editor_track::point_ref>> editor_track::connecte
return result;
}
namespace
{
// direction of the path at the end, into the path
glm::dvec2 end_direction(segment_data const &Path, editor_track::point_kind const Kind)
{
bool const start{Kind == editor_track::point_kind::start};
auto const &point{Path.points[start ? segment_data::point::start : segment_data::point::end]};
auto const &control{Path.points[start ? segment_data::point::control1 : segment_data::point::control2]};
auto const direction{plan_of(control != glm::dvec3{} ? control : Path.points[start ? segment_data::point::end : segment_data::point::start] - point)};
return glm::length(direction) > 1e-9 ? glm::normalize(direction) : glm::dvec2{0.0, 1.0};
}
} // namespace
std::vector<editor_track::joint> editor_track::joints(TTrack const &Track)
{
std::vector<joint> result;
for (int i = 0; i < static_cast<int>(Track.m_paths.size()); ++i)
{
for (auto const kind : {point_kind::start, point_kind::end})
{
point_ref const own{i, kind};
for (auto const &connection : connected_points(Track, point_position(Track, own)))
{
auto *other{connection.first};
std::string reason;
if (has_switch_paths(*other) || false == can_edit_geometry(*other, reason))
continue;
if (std::any_of(result.begin(), result.end(), [&](joint const &Joint) { return Joint.other == other && Joint.theirs == connection.second; }))
continue;
auto const &path{other->m_paths[connection.second.path]};
bool const start{connection.second.kind == point_kind::start};
auto control{path.points[start ? segment_data::point::control1 : segment_data::point::control2]};
if (control == glm::dvec3{})
control = (path.points[start ? segment_data::point::end : segment_data::point::start] - path.points[start ? segment_data::point::start : segment_data::point::end]) / 3.0;
result.push_back({own, other, connection.second, end_direction(Track.m_paths[i], kind), control});
}
}
}
return result;
}
void editor_track::follow(TTrack const &Track, std::vector<joint> const &Joints)
{
for (auto const &joint : Joints)
{
if (joint.own.path >= static_cast<int>(Track.m_paths.size()) || joint.theirs.path >= static_cast<int>(joint.other->m_paths.size()))
continue;
move_point(*joint.other, joint.theirs, point_position(Track, joint.own));
auto const angle{signed_angle(joint.direction, end_direction(Track.m_paths[joint.own.path], joint.own.kind))};
auto const moved{turned(plan_of(joint.control), angle)};
joint.other->m_paths[joint.theirs.path].points[joint.theirs.kind == point_kind::start ? segment_data::point::control1 : segment_data::point::control2] = {moved.x, joint.control.y, moved.y};
}
}
bool editor_track::is_connected(TTrack const &Track, point_ref const &Point)
{
if (false == editor_track::is_end(Point.kind))
@@ -395,12 +544,16 @@ bool editor_track::is_connected(TTrack const &Track, point_ref const &Point)
}
editor_track::snap_target editor_track::find_snap_target(TTrack const &Track, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> const &Exclude)
{
return find_free_end(&Track, Track.iCategoryFlag & 15, Position, Radius, Exclude);
}
editor_track::snap_target editor_track::find_free_end(TTrack const *Self, int const Category, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> const &Exclude)
{
snap_target result;
result.distance = std::numeric_limits<double>::max();
auto const excluded = [&](TTrack const *Other) { return std::find(Exclude.begin(), Exclude.end(), Other) != Exclude.end(); };
auto const category{Track.iCategoryFlag & 15};
auto const sections{simulation::Region->sections(Position, static_cast<float>(Radius))};
for (auto *section : sections)
{
@@ -408,7 +561,7 @@ editor_track::snap_target editor_track::find_snap_target(TTrack const &Track, gl
{
for (auto *other : cell.m_directories.paths)
{
if (other == &Track || excluded(other) || (other->iCategoryFlag & 15) != category || false == is_supported(*other))
if (other == Self || excluded(other) || (other->iCategoryFlag & 15) != Category || false == is_supported(*other))
continue;
for (int i = 0; i < static_cast<int>(other->m_paths.size()); ++i)
{
@@ -693,7 +846,7 @@ void editor_track::store_switch_path(TTrack &Switch, int const Path)
}
}
bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::string &Error)
bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::string &Error, bool const Switches)
{
Chain = {};
if (From == nullptr || To == nullptr)
@@ -717,10 +870,25 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin
{
Chain.tracks = {From};
Chain.forward = {throughend};
Chain.switches.clear();
TTrack *previous{From};
TTrack *current{throughend ? From->trNext : From->trPrev};
while (current != nullptr && current != From && Chain.tracks.size() < 10000 && regular(current))
while (current != nullptr && current != From && Chain.tracks.size() + Chain.switches.size() < 10000)
{
if (Switches && current->eType == tt_Switch && has_switch_paths(*current) && is_supported(*current))
{
// only along the main track, the way in decides the way out
auto const &extension{*current->SwitchExtension};
bool const atstart{extension.pPrevs[0] == previous};
if (atstart == (extension.pNexts[0] == previous))
break;
Chain.switches.push_back({current, atstart, Chain.tracks.size()});
previous = current;
current = atstart ? extension.pNexts[0] : extension.pPrevs[0];
continue;
}
if (false == regular(current))
break;
auto const entersatstart{current->trPrev == previous};
Chain.tracks.push_back(current);
Chain.forward.push_back(entersatstart);
@@ -738,12 +906,29 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin
if (false == found)
{
Chain = {};
Error = "The selected paths aren't connected by a chain of regular paths (switches end the chain)";
Error = Switches ? "The selected paths aren't connected along the main tracks of the switches" : "The selected paths aren't connected by a chain of regular paths (switches end the chain)";
return false;
}
}
for (auto const &passage : Chain.switches)
{
if ((passage.track->iCategoryFlag & 15) != (From->iCategoryFlag & 15))
{
Error = "The fragment mixes different kinds of paths";
Chain = {};
return false;
}
Chain.length += bezier{passage.track->m_paths.front()}.plan_length();
}
auto const category{From->iCategoryFlag & 15};
return complete_chain(Chain, Error);
}
bool editor_track::complete_chain(chain &Chain, std::string &Error)
{
auto *from{Chain.tracks.front()};
auto *to{Chain.tracks.back()};
auto const category{from->iCategoryFlag & 15};
for (auto const *track : Chain.tracks)
{
if ((track->iCategoryFlag & 15) != category)
@@ -761,24 +946,45 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin
Chain.radius = radius;
}
auto const &first{From->m_paths.front()};
auto const &first{from->m_paths.front()};
auto const firstforward{Chain.forward.front()};
Chain.start = first.points[firstforward ? segment_data::point::start : segment_data::point::end];
Chain.start_direction = firstforward ? path_tangent(first, false) : -path_tangent(first, true);
adjoining_direction(firstforward ? From->trPrev : From->trNext, Chain.start, false, Chain.start_direction, Chain.start_radius);
adjoining_direction(firstforward ? from->trPrev : from->trNext, Chain.start, false, Chain.start_direction, Chain.start_radius);
Chain.start_direction = glm::normalize(Chain.start_direction);
auto const &last{To->m_paths.front()};
auto const &last{to->m_paths.front()};
auto const lastforward{Chain.forward.back()};
Chain.end = last.points[lastforward ? segment_data::point::end : segment_data::point::start];
Chain.end_direction = lastforward ? path_tangent(last, true) : -path_tangent(last, false);
adjoining_direction(lastforward ? To->trNext : To->trPrev, Chain.end, true, Chain.end_direction, Chain.end_radius);
adjoining_direction(lastforward ? to->trNext : to->trPrev, Chain.end, true, Chain.end_direction, Chain.end_radius);
Chain.end_direction = glm::normalize(Chain.end_direction);
Error.clear();
return true;
}
std::vector<editor_track::chain> editor_track::chain_parts(chain const &Chain)
{
std::vector<chain> parts;
std::size_t first{0};
auto const add = [&](std::size_t const End) {
if (End <= first)
return;
chain part;
part.tracks.assign(Chain.tracks.begin() + first, Chain.tracks.begin() + End);
part.forward.assign(Chain.forward.begin() + first, Chain.forward.begin() + End);
std::string error;
if (complete_chain(part, error))
parts.push_back(std::move(part));
first = End;
};
for (auto const &passage : Chain.switches)
add(passage.before);
add(Chain.tracks.size());
return parts;
}
namespace
{
@@ -874,6 +1080,11 @@ void follow_joint(TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, d
} // namespace
void editor_track::place(TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, double const Angle)
{
rigid_move(Track, From, To, Angle);
}
bool editor_track::is_straight(TTrack const &Track, straight_tolerance const &Tolerance)
{
if (false == is_supported(Track) || Track.m_editorremoved)
@@ -1034,7 +1245,15 @@ bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance
current = entersatstart ? current->trNext : current->trPrev;
}
}
return analyse_curve({run.begin(), run.end()}, Gauge, Curve);
}
bool editor_track::analyse_curve(std::vector<std::pair<TTrack *, bool>> const &Run, double const Gauge, curve &Curve)
{
Curve = {};
if (Run.empty())
return false;
auto const &run{Run};
std::vector<double> turns;
std::vector<double> lengths;
std::vector<bool> varying;
@@ -1165,6 +1384,102 @@ bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance
return true;
}
bool editor_track::find_run(TTrack &Track, double const Limit, chain &Chain, std::string &Error)
{
auto const category{Track.iCategoryFlag & 15};
auto const regular = [&](TTrack const *Other) { return Other != nullptr && Other->eType == tt_Normal && is_supported(*Other) && false == Other->m_editorremoved && (Other->iCategoryFlag & 15) == category; };
if (false == regular(&Track))
{
Error = "A line is made of regular paths, a switch ends it";
return false;
}
straight_tolerance const tolerance;
TTrack *ends[2]{&Track, &Track};
std::vector<TTrack const *> visited{&Track};
for (int side = 0; side < 2; ++side)
{
TTrack *previous{&Track};
TTrack *current{side == 0 ? Track.trNext : Track.trPrev};
double length{0.0};
// past the limit the line goes on to the next straight, so it doesn't end inside a curve
while (regular(current) && std::find(visited.begin(), visited.end(), current) == visited.end() && (length < Limit || (length < 3.0 * Limit && false == is_straight(*ends[side], tolerance))))
{
visited.push_back(current);
ends[side] = current;
length += current->Length();
auto const entersatstart{current->trPrev == previous};
previous = current;
current = entersatstart ? current->trNext : current->trPrev;
}
}
return find_chain(ends[1], ends[0], Chain, Error);
}
std::vector<editor_track::line_vertex> editor_track::recognize_line(chain const &Chain, straight_tolerance const &Tolerance, double const Gauge)
{
std::vector<line_vertex> result;
auto const count{Chain.tracks.size()};
auto const path = [&](std::size_t const Index) -> segment_data const & { return Chain.tracks[Index]->m_paths.front(); };
auto const tangent_in = [&](std::size_t const Index) { return glm::normalize(plan_of(Chain.forward[Index] ? path_tangent(path(Index), false) : -path_tangent(path(Index), true))); };
auto const tangent_out = [&](std::size_t const Index) { return glm::normalize(plan_of(Chain.forward[Index] ? path_tangent(path(Index), true) : -path_tangent(path(Index), false))); };
auto const point_in = [&](std::size_t const Index) { return plan_of(path(Index).points[Chain.forward[Index] ? segment_data::point::start : segment_data::point::end]); };
auto const point_out = [&](std::size_t const Index) { return plan_of(path(Index).points[Chain.forward[Index] ? segment_data::point::end : segment_data::point::start]); };
std::vector<bool> straight(count);
std::vector<double> turns(count);
for (std::size_t i = 0; i < count; ++i)
{
straight[i] = is_straight(*Chain.tracks[i], Tolerance);
turns[i] = signed_angle(tangent_in(i), tangent_out(i));
}
auto const add_curve = [&](std::size_t const First, std::size_t const Last) {
auto const a{point_in(First)};
auto const b{point_out(Last)};
auto const directionin{tangent_in(First)};
auto const directionout{tangent_out(Last)};
auto const determinant{cross(directionin, directionout)};
if (std::abs(determinant) < 1e-6)
return;
line_vertex vertex;
vertex.position = a + directionin * (cross(b - a, directionout) / determinant);
std::vector<std::pair<TTrack *, bool>> run;
for (auto i = First; i <= Last; ++i)
run.emplace_back(Chain.tracks[i], Chain.forward[i]);
analyse_curve(run, Gauge, vertex.shape);
result.push_back(vertex);
};
std::size_t i{0};
while (i < count)
{
if (straight[i])
{
if (i + 1 < count && straight[i + 1] && std::abs(signed_angle(tangent_out(i), tangent_in(i + 1))) > 1e-3)
{
line_vertex vertex;
vertex.position = point_out(i);
vertex.kink = true;
result.push_back(vertex);
}
++i;
continue;
}
auto const first{i};
int sign{0};
while (i < count && false == straight[i])
{
int const current{turns[i] > 1e-9 ? 1 : turns[i] < -1e-9 ? -1 : 0};
// a reverse curve: the next one starts here
if (current != 0 && sign != 0 && current != sign)
break;
if (current != 0)
sign = current;
++i;
}
add_curve(first, i - 1);
}
return result;
}
namespace
{
@@ -1649,25 +1964,39 @@ std::vector<editor_track::height_gap> editor_track::apply_profile(route &Route,
sample_route(Route, 1.0);
std::vector<height_gap> gaps;
std::vector<TTrack *> raised;
for (auto const &joint : joints)
{
auto const &end{joint.track->m_paths[joint.path].points[joint.atend ? segment_data::point::end : segment_data::point::start]};
double nearest{std::numeric_limits<double>::max()};
double gap{0.0};
for (auto const &path : joint.neighbour->m_paths)
glm::dvec3 *closest{nullptr};
for (auto &path : joint.neighbour->m_paths)
for (auto const index : {segment_data::point::start, segment_data::point::end})
{
auto const &point{path.points[index]};
auto &point{path.points[index]};
auto const distance{std::hypot(point.x - end.x, point.z - end.z)};
if (distance < nearest)
{
nearest = distance;
gap = point.y - end.y;
closest = &point;
}
}
if (nearest <= kSamePoint && std::abs(gap) > 0.001)
gaps.push_back({joint.track, joint.neighbour, gap});
if (nearest > kSamePoint || std::abs(gap) <= 0.001)
continue;
// a regular path off the route takes the height of the switch at the joint, keeping its other end
std::string reason;
bool const closed{joint.neighbour->eType == tt_Normal && can_edit_geometry(*joint.neighbour, reason)};
if (closed)
{
remember(joint.neighbour);
closest->y = end.y;
add_unique(raised, joint.neighbour);
}
gaps.push_back({joint.track, joint.neighbour, gap, closed});
}
commit(raised);
return gaps;
}
@@ -1844,7 +2173,7 @@ void write_header(std::ostream &Text, char const *Type, double const Length, dou
{
char const *environments[] = {"flat", "mountains", "canyon", "tunnel", "bridge", "bank"};
auto const environment{Style.eEnvironment >= e_flat && Style.eEnvironment <= e_bank ? environments[Style.eEnvironment] : "flat"};
Text << Type << ' ' << Length << ' ' << Style.fTrackWidth << ' ' << Style.fFriction << ' ' << Sound << ' ' << Style.iQualityFlag << ' ' << 0 << ' ' << environment << ' ';
Text << Type << ' ' << Length << ' ' << Style.fTrackWidth << ' ' << Style.fFriction << ' ' << Sound << ' ' << Style.iQualityFlag << ' ' << (Style.iDamageFlag & 127) << ' ' << environment << ' ';
if (Style.m_visible)
Text << "vis " << editor_track::material_name(Style.m_material1) << ' ' << Style.fTexLength << ' ' << editor_track::material_name(Second) << ' ' << editor_track::texture_height(Style) << ' ' << Style.fTexWidth << ' ' << Style.fTexSlope << ' ';
else
@@ -1864,6 +2193,13 @@ void write_ending(std::ostream &Text, TTrack const &Style)
{
if ((Style.iCategoryFlag & 15) == 1 && false == Style.m_profile1.first.empty())
Text << "railprofile " << Style.m_profile1.first << ' ';
// a piece of the track made from another one stays in its track circuit and under the same overhead line
for (auto const *isolated : Style.Isolated)
Text << "isolated " << isolated->asName << ' ';
if (Style.fOverhead != -1.0)
Text << "overhead " << Style.fOverhead << ' ';
if (Style.m_sleeper_enabled && false == Style.m_sleeper_model_name.empty())
Text << "sleepermodel " << Style.m_sleeper_frequency << ' ' << Style.m_sleeper_model_name << ' ' << (Style.m_sleeper_skin_name.empty() ? std::string{"none"} : Style.m_sleeper_skin_name) << ' ' << Style.m_sleeper_offset.x << ' ' << Style.m_sleeper_offset.y << ' ' << Style.m_sleeper_offset.z << ' ' << Style.m_sleeper_ballast_z << ' ';
Text << "endtrack\n";
}
@@ -2203,7 +2539,9 @@ TTrack *editor_track::split_path(TTrack &Track, double const T)
auto const roll{static_cast<float>(path.rolls[0] + (path.rolls[1] - path.rolls[0]) * T)};
if (path.points[segment_data::point::control1] == glm::dvec3{} && path.points[segment_data::point::control2] == glm::dvec3{})
{
auto const middle{glm::mix(p0, p3, T)};
// the parameter is the one of the cubic with the controls at the ends, as everywhere else, not a share of the length
auto const u{1.0 - T};
auto const middle{p0 * (u * u * u + 3.0 * u * u * T) + p3 * (3.0 * u * T * T + T * T * T)};
first.points[segment_data::point::end] = middle;
second.points[segment_data::point::start] = middle;
}
@@ -2228,6 +2566,24 @@ TTrack *editor_track::split_path(TTrack &Track, double const T)
second.rolls[0] = roll;
auto *created{create_path(Track, second)};
Track.m_paths.front() = first;
// the events stay where the trains run into them: those of the runs towards the end at the start, in the original,
// those of the runs towards the start at the end, in the new piece; those of a stop in both. the ones bound
// by the name of the path stay with it
for (auto const index : {0, 3})
for (auto const &event : events(Track, index))
if (false == event.first.empty())
events(*created, index).push_back(event);
for (auto const index : {1, 4})
{
auto &own{events(Track, index)};
for (auto const &event : own)
if (false == event.first.empty())
events(*created, index).push_back(event);
own.erase(std::remove_if(own.begin(), own.end(), [](auto const &Event) { return false == Event.first.empty(); }), own.end());
}
std::string missing;
bind_events(Track, missing);
bind_events(*created, missing);
if (s_observer != nullptr)
s_observer->track_split(Track, *created, first);
commit({&Track, created});
@@ -2235,18 +2591,43 @@ TTrack *editor_track::split_path(TTrack &Track, double const T)
}
TTrack *editor_track::load_path(std::string const &Text, TTrack const &Template, std::string const &Name)
{
return load_text(Text, &Template, Name);
}
TTrack *editor_track::create_path(path_style const &Style, segment_data const &Path)
{
std::ostringstream text;
text.precision(std::numeric_limits<double>::digits10);
auto const length{glm::distance(Path.points[segment_data::point::start], Path.points[segment_data::point::end])};
text << "normal " << length << " 1.435 0.15 25 20 0 flat vis " << Style.rail << " 6 " << Style.ballast << " 0.2 0.5 1.1 ";
write_points(text, Path);
if (Style.velocity > 0.0)
text << "velocity " << Style.velocity << ' ';
if (false == Style.rail_profile.empty())
text << "railprofile " << Style.rail_profile << ' ';
text << "endtrack\n";
auto *track{load_text(text.str(), nullptr, {})};
track->m_paths = {Path};
return track;
}
TTrack *editor_track::load_text(std::string const &Text, TTrack const *Template, std::string const &Name)
{
cParser parser(Text, cParser::buffer_TEXT);
scene::node_data data;
data.type = "track";
auto const base{Template.name().empty() || Template.name() == "none" ? std::string{"editor_track"} : Template.name()};
auto const base{false == Name.empty() ? Name : Template == nullptr || Template->name().empty() || Template->name() == "none" ? std::string{"editor_track"} : Template->name()};
data.name = Name;
data.layer = Template.layer();
data.layer = Template != nullptr ? Template->layer() : scene::Layers.active();
for (int i = 1; data.name.empty() || simulation::Paths.find(data.name) != nullptr; ++i)
data.name = base + "_" + std::to_string(i);
auto *track = new TTrack(data);
track->m_rangesquaredmin = Template.m_rangesquaredmin;
track->m_rangesquaredmax = Template.m_rangesquaredmax;
if (Template != nullptr)
{
track->m_rangesquaredmin = Template->m_rangesquaredmin;
track->m_rangesquaredmax = Template->m_rangesquaredmax;
}
track->Load(&parser, glm::dvec3{});
for (auto *events : {&track->m_events0, &track->m_events1, &track->m_events2, &track->m_events0all, &track->m_events1all, &track->m_events2all})
events->clear();

View File

@@ -20,6 +20,7 @@ http:
#include "utilities/Classes.h"
class TTrack;
class basic_event;
class editor_track
{
@@ -52,10 +53,29 @@ class editor_track
float texheight{0.6f};
float texwidth{0.9f};
float texslope{0.9f};
std::vector<std::string> isolated;
float overhead{-1.f};
bool sleepers{false};
float sleeper_frequency{0.6f};
std::string sleeper_model;
std::string sleeper_skin;
glm::vec3 sleeper_offset{0.f};
float sleeper_ballast{0.f};
std::array<std::vector<std::pair<std::string, basic_event *>>, 6> events;
};
static state capture(TTrack const &Track);
static void apply(TTrack &Track, state const &State);
// lists of the events of the path, in the order of event_keyword(): event0, event1, event2, eventall0, eventall1, eventall2
static std::vector<std::pair<std::string, basic_event *>> &events(TTrack &Track, int const Index);
static char const *event_keyword(int const Index);
// looks the events up by their names; the names of those which don't exist are given in Missing
static void bind_events(TTrack &Track, std::string &Missing);
// track circuits the path belongs to, made when they don't exist yet
static void isolated(TTrack &Track, std::vector<std::string> const &Names);
// -1: normal, 0: no current, above 0: speed limit with the pantographs lowered
static void overhead(TTrack &Track, float const Overhead);
static bool can_edit_geometry(TTrack const &Track, std::string &Reason);
static bool is_supported(TTrack const &Track);
@@ -71,6 +91,19 @@ class editor_track
static std::vector<TTrack *> neighbours(TTrack const &Track);
static std::vector<std::pair<TTrack *, point_ref>> connected_points(TTrack const &Track, glm::dvec3 const &Position);
static bool is_connected(TTrack const &Track, point_ref const &Point);
// end of a regular neighbouring path joined to an end of the track, as it was when taken
struct joint
{
point_ref own;
TTrack *other{nullptr};
point_ref theirs;
glm::dvec2 direction{0.0, 1.0}; // of the track at its end, into it
glm::dvec3 control{0.0}; // of the neighbour at its end, never zero
};
// joints the editor can change: switches and paths which can't be edited aren't taken
static std::vector<joint> joints(TTrack const &Track);
// moves the joined ends to where the ends of the track are now, turning them as the track turned there
static void follow(TTrack const &Track, std::vector<joint> const &Joints);
struct snap_target
{
@@ -81,6 +114,8 @@ class editor_track
double distance{0.0};
};
static snap_target find_snap_target(TTrack const &Track, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> const &Exclude);
// nearest end of a path of the category which nothing else joins; Self may be null
static snap_target find_free_end(TTrack const *Self, int const Category, glm::dvec3 const &Position, double const Radius, std::vector<TTrack const *> const &Exclude);
static void snap_point(TTrack &Track, point_ref const &Point, snap_target const &Target, bool const Aligntangent);
struct chain
@@ -96,8 +131,21 @@ class editor_track
double length{0.0};
double velocity{-1.0};
double radius{0.0};
// switches the chain runs through along their main track, each before the regular path of the index given
struct passage
{
TTrack *track{nullptr};
bool forward{true};
std::size_t before{0};
};
std::vector<passage> switches;
};
static bool find_chain(TTrack *From, TTrack *To, chain &Chain, std::string &Error);
// with Switches the chain may run through the switches along their main tracks; its ends are regular paths
static bool find_chain(TTrack *From, TTrack *To, chain &Chain, std::string &Error, bool const Switches = false);
// the regular parts of a chain between its switches, empty ones skipped
static std::vector<chain> chain_parts(chain const &Chain);
// moves the track as a whole, From going to To, turned by Angle in the plan
static void place(TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, double const Angle);
struct straight
{
@@ -140,6 +188,18 @@ class editor_track
double split{0.5};
};
static bool find_curve(TTrack &Track, straight_tolerance const &Tolerance, double const Gauge, curve &Curve);
// curve made of the given paths, each with the flag of being run from its start
static bool analyse_curve(std::vector<std::pair<TTrack *, bool>> const &Run, double const Gauge, curve &Curve);
// regular paths in line through the track, up to switches, free ends or about Limit metres each way
static bool find_run(TTrack &Track, double const Limit, chain &Chain, std::string &Error);
// the chain read as an alignment: a vertex at the intersection of the tangents of each curve
struct line_vertex
{
glm::dvec2 position{0.0};
curve shape;
bool kink{false}; // the straights meet with no curve between them
};
static std::vector<line_vertex> recognize_line(chain const &Chain, straight_tolerance const &Tolerance, double const Gauge);
// regular straight paths continuing a chain end in line, which the chain can take over
struct straight_run
{
@@ -184,15 +244,17 @@ class editor_track
static glm::dvec3 sampled_position(std::vector<route_sample> const &Samples, double const Chainage);
// grade of the path which adjoins the end of the route, positive rising along the route
static bool adjoining_grade(route const &Route, bool const Atend, double &Grade);
// path off the route whose end no longer meets the adjoining path in height
// path off the route whose end no longer met the adjoining path in height
struct height_gap
{
TTrack *track{nullptr};
TTrack *neighbour{nullptr};
double gap{0.0}; // neighbour - track
bool closed{false}; // the end of the neighbour was brought to the track
};
// sets elevations of the paths of the route; regular paths are split at the chainages in Breaks,
// switches are tilted as a whole in the plane of their grade, branches included
// switches are tilted as a whole in the plane of their grade, branches included, and the regular paths
// off the route get their ends joined to the switches brought to the height there
static std::vector<height_gap> apply_profile(route &Route, std::function<double(double)> const &Elevation, std::function<double(double)> const &Grade, std::vector<double> const &Breaks, std::vector<std::pair<TTrack *, state>> &States, std::vector<TTrack *> &Created);
// carries elevations of the chain over to the new pieces, in proportion of the length
static void keep_heights(chain const &Chain, std::vector<segment_data> &Pieces);
@@ -214,6 +276,16 @@ class editor_track
static std::vector<switch_template> find_switch_templates();
static TTrack *load_path(std::string const &Text, TTrack const &Template, std::string const &Name = {});
static TTrack *create_path(TTrack const &Style, segment_data const &Path);
// parameters of a path laid where there's no other path to copy them from
struct path_style
{
double velocity{100.0};
std::string rail{"rail_screw_used1"};
std::string ballast{"1435mm/tpbps-new2"};
std::string rail_profile;
};
// the path goes to the layer receiving the items created in the editor
static TTrack *create_path(path_style const &Style, segment_data const &Path);
static TTrack *split_path(TTrack &Track, double const T);
static double nearest_parameter(TTrack const &Track, glm::dvec3 const &Point);
// point of the first path at the parameter of nearest_parameter()
@@ -248,6 +320,8 @@ class editor_track
private:
static observer *s_observer;
// sets the length, the ends and their directions of the chain of the tracks given
static bool complete_chain(chain &Chain, std::string &Error);
static void disconnect(TTrack &Track);
static void join(TTrack &Track);
static void connect(TTrack &Track, bool const Prevside, TTrack *Other, int const Endpointid);
@@ -256,5 +330,6 @@ class editor_track
static void store_switch_path(TTrack &Switch, int const Path);
static void rebuild_geometry(TTrack &Track);
static TTrack *clone(TTrack const &Template);
static TTrack *load_text(std::string const &Text, TTrack const *Template, std::string const &Name);
static void move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End, std::function<bool(TTrack const *)> const &Member);
};

View File

@@ -792,6 +792,8 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
edit.node = Node;
state.patches[Layer].edits.emplace_back(std::move(edit));
};
// changes which can't be written, as their definition stands for more than one node
std::vector<std::string> unwritten;
// traction and event launchers are rewritten when the editor moved them along with the track they belong to
std::vector<basic_node *> patchednodes;
auto const patch_node = [&](basic_node *Node, auto const &Patch) {
@@ -801,10 +803,15 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
return true;
}
auto const &source{lookup->second};
if (false == is_output(resolve(source.layer)) || false == writable(source.layer))
if (false == is_output(resolve(source.layer)))
{
return true;
}
if (false == writable(source.layer))
{
unwritten.push_back(Node->name());
return true;
}
if (false == load(source.layer))
{
return false;
@@ -876,15 +883,22 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
{
continue;
}
created[nodelayer].emplace_back(path, path_text(*path, layer(nodelayer).context_insert().offset));
// a file included more than once would get a copy of the new path at every inclusion
auto const target{writable(nodelayer) ? nodelayer : root};
created[target].emplace_back(path, path_text(*path, layer(target).context_insert().offset));
savedpaths.push_back(path);
continue;
}
auto const &source{lookup->second};
if ((false == path->m_editorremoved && false == path->dirty()) || false == writable(source.layer))
if (false == path->m_editorremoved && false == path->dirty())
{
continue;
}
if (false == writable(source.layer))
{
unwritten.push_back(path->name().empty() ? std::string{"(noname)"} : path->name());
continue;
}
if (false == load(source.layer))
{
return fail(state.error);
@@ -1636,6 +1650,21 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
result.message += file + (&file != &result.files.back() ? ", " : "");
}
}
if (false == unwritten.empty())
{
std::string names;
for (std::size_t i = 0; i < std::min<std::size_t>(unwritten.size(), 5); ++i)
{
names += (i > 0 ? ", " : "") + unwritten[i];
}
if (unwritten.size() > 5)
{
names += ", ...";
}
auto const warning{std::to_string(unwritten.size()) + " changed nodes not saved, their files are included with parameters or more than once: " + names};
WriteLog("Scenery save: " + warning, logtype::generic);
result.message = result.files.empty() ? warning : result.message + ". " + warning;
}
return result;
}