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:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user