mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-26 15:54:53 +01:00
feat(editor): the signals and the signs by a track go with it as the Plan lays it anew, square to the line, not along it
A curve laid anew, its radius changed or made compound, or a straight moved: the paths of the chain are cut again where their joints stood, set square onto the new line, each keeping its stretch of the ground, so their events, their track circuits and the cuts of the block sections stay where they were instead of being handed on along the line; the parts between the switches alike, the old even sharing kept where the joints don't follow one another along the new line. The objects by a track taken for a change go with it by themselves: the signals and the warning discs it or its run reads, the SHP resonators by their events, and the controls of the block sections, the hectometre posts, the W17 markers and the other signs by the track nearest to them, within 12 m. They move square to the new axis keeping their offset and their turn, an object moved by hand meanwhile staying where it was put. Such a binding isn't kept in the scenery nor shown among the bound objects of the infra tool, which can still bind the object for good, and the undo and the redo take the objects back and forth with the tracks. The status of the Plan tells how many objects moved with the tracks. A path taking an object over the other way round keeps it on its side, turned as it was.
This commit is contained in:
@@ -2372,6 +2372,23 @@ class editor_mode : public application_mode, private editor_track::observer, pri
|
||||
std::vector<infra::object_state> m_infra_buffer; // states of the objects before the change in progress
|
||||
std::vector<std::pair<scene::instance_handle, std::string>> m_drive_buffer;
|
||||
bool m_infra_suspended{false};
|
||||
// the point objects which go with a path: the signals and the warning discs it reads, the SHP resonators, and the controls of the
|
||||
// block sections, the signs and the posts by the path nearest to them
|
||||
struct point_object
|
||||
{
|
||||
scene::instance_handle include{0};
|
||||
glm::dvec3 location{0.0};
|
||||
TTrack *reader{nullptr};
|
||||
TTrack *nearest{nullptr}; // of one no path reads
|
||||
infra::category group{infra::category::other};
|
||||
};
|
||||
std::vector<point_object> m_point_objects;
|
||||
std::size_t m_point_objects_revision{static_cast<std::size_t>(-1)};
|
||||
std::size_t m_point_objects_count{0};
|
||||
void infra_adopt(TTrack &Track);
|
||||
int infra_bound_count() const; // by the infra tool, not the ones a change of their track took along
|
||||
std::set<std::pair<scene::basic_node const *, scene::instance_handle>> m_infra_carried; // the objects their tracks moved since the tool cleared it
|
||||
std::string infra_carried_note(); // a line of the status telling how many, empty for none; clears them
|
||||
void infra_load();
|
||||
void infra_store();
|
||||
void infra_forget(scene::basic_node const *Node);
|
||||
|
||||
@@ -389,9 +389,9 @@ std::string editor_mode::digitize_scenery(bool const Write)
|
||||
m_infra.scope = 3;
|
||||
infra_recognize();
|
||||
report.found = static_cast<int>(m_infra.candidates.size());
|
||||
auto const before{m_bindings.size()};
|
||||
auto const before{infra_bound_count()};
|
||||
infra_bind_chosen();
|
||||
report.bound = static_cast<int>(m_bindings.size() - before);
|
||||
report.bound = infra_bound_count() - before;
|
||||
m_infra.scope = scope;
|
||||
}
|
||||
auto text{format(STR_C("Switches: %d, kept already %d, of the catalogue %d, measured %d, parts of slips %d, odd %d"), report.switches, report.known, report.designed, report.measured, report.slips, report.odd)};
|
||||
|
||||
@@ -49,6 +49,11 @@ using geometry::plan_distance;
|
||||
using geometry::plan_of;
|
||||
|
||||
std::string const kBindingMark{"//$b"};
|
||||
// the templates of the signalling which go with a path without a signal template of their own: the SHP resonator, read by its
|
||||
// event, and the control of a block section, by the path nearest to it
|
||||
std::string const kResonatorFile{"rezonator.inc"};
|
||||
std::string const kControlFile{"sbl_odstep.inc"};
|
||||
double constexpr kAdoptReach{12.0}; // m, of a point object from the axis of its path
|
||||
|
||||
template <typename Type_>
|
||||
bool contains(std::vector<Type_> const &Items, Type_ const &Item)
|
||||
@@ -803,7 +808,7 @@ void editor_mode::infra_store()
|
||||
{
|
||||
std::vector<glm::dvec3> points;
|
||||
std::optional<double> yaw;
|
||||
if (binding.anchors.empty() || false == object_points(binding, points, yaw))
|
||||
if (binding.adopted || binding.anchors.empty() || false == object_points(binding, points, yaw))
|
||||
continue;
|
||||
auto layer{scene::Layers.resolve(binding.anchors.front().at.track->layer())};
|
||||
if (false == scene::Layers.valid(layer) || false == scene::Layers.accepts(layer))
|
||||
@@ -870,6 +875,18 @@ infra::binding *editor_mode::infra_find(infra::binding const &Binding)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string editor_mode::infra_carried_note()
|
||||
{
|
||||
auto const count{m_infra_carried.size()};
|
||||
m_infra_carried.clear();
|
||||
return count > 0 ? format(STR_P("\n%zu object by the tracks moved sideways with them", "\n%zu objects by the tracks moved sideways with them", count), count) : std::string{};
|
||||
}
|
||||
|
||||
int editor_mode::infra_bound_count() const
|
||||
{
|
||||
return static_cast<int>(std::count_if(m_bindings.begin(), m_bindings.end(), [](infra::binding const &Binding) { return false == Binding.adopted; }));
|
||||
}
|
||||
|
||||
void editor_mode::infra_recognize()
|
||||
{
|
||||
auto &state{m_infra};
|
||||
@@ -977,7 +994,8 @@ void editor_mode::infra_recognize()
|
||||
Binding.label = object_label(Binding);
|
||||
candidate.offset = Binding.anchors.front().offset;
|
||||
candidate.reason = Reason;
|
||||
candidate.bound = (infra_find(Binding) != nullptr);
|
||||
auto const *existing{infra_find(Binding)};
|
||||
candidate.bound = (existing != nullptr && false == existing->adopted);
|
||||
candidate.chosen = (false == candidate.bound && Binding.group != infra::category::other && Binding.group != infra::category::environment);
|
||||
candidate.binding = std::move(Binding);
|
||||
state.read += candidate.reader != nullptr ? 1 : 0;
|
||||
@@ -1153,6 +1171,133 @@ void editor_mode::infra_buffer(infra::binding const &Binding, bool const Refresh
|
||||
m_infra_buffer.push_back(infra_state_of(Binding));
|
||||
}
|
||||
|
||||
void editor_mode::infra_adopt(TTrack &Track)
|
||||
{
|
||||
auto const revision{history_revision()};
|
||||
if (revision != m_point_objects_revision || scene::Layers.instance_count() != m_point_objects_count)
|
||||
{
|
||||
m_point_objects_revision = revision;
|
||||
m_point_objects_count = scene::Layers.instance_count();
|
||||
m_point_objects.clear();
|
||||
std::unordered_map<std::string, TTrack *> readers;
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
if (track != nullptr && false == track->m_editorremoved)
|
||||
for (int list = 0; list < 6; ++list)
|
||||
for (auto const &event : editor_track::events(*track, list))
|
||||
if (false == event.first.empty())
|
||||
readers.emplace(ToLower(event.first), track);
|
||||
static auto const rules{infra::default_rules()};
|
||||
for (scene::instance_handle handle = 1; handle <= scene::Layers.instance_count(); ++handle)
|
||||
{
|
||||
include_place place;
|
||||
if (false == include_place_of(handle, place) || place.values.empty() || false == scene::Layers.removable(handle))
|
||||
continue;
|
||||
point_object object;
|
||||
object.include = handle;
|
||||
object.location = include_location(place);
|
||||
auto const file{ToLower(place.file)};
|
||||
auto const *signal{signal_template_of(place.file)};
|
||||
std::string read;
|
||||
if (signal != nullptr)
|
||||
{
|
||||
object.group = infra::category::signal;
|
||||
read = signal->read.empty() ? std::string{} : editor_includes::substitute(signal->read, place.values);
|
||||
}
|
||||
else if (file == kResonatorFile)
|
||||
{
|
||||
object.group = infra::category::equipment;
|
||||
read = place.values.front() + "_shp";
|
||||
}
|
||||
else if (file == kControlFile)
|
||||
object.group = infra::category::logic;
|
||||
else
|
||||
{
|
||||
auto const named{infra::category_named(place.category)};
|
||||
object.group = named.has_value() ? *named : infra::classify(place.file + ' ' + place.description, rules);
|
||||
if (object.group != infra::category::signal && object.group != infra::category::sign)
|
||||
continue;
|
||||
}
|
||||
if (false == read.empty())
|
||||
if (auto const reader{readers.find(ToLower(read))}; reader != readers.end())
|
||||
object.reader = reader->second;
|
||||
m_point_objects.push_back(std::move(object));
|
||||
}
|
||||
// the path nearest to each one no path reads, the objects by cells of the plan
|
||||
auto constexpr cell{200.0};
|
||||
auto const key = [&](double const Value) { return static_cast<long long>(std::floor(Value / cell)); };
|
||||
std::map<std::pair<long long, long long>, std::vector<std::size_t>> cells;
|
||||
for (std::size_t i = 0; i < m_point_objects.size(); ++i)
|
||||
if (m_point_objects[i].reader == nullptr)
|
||||
cells[{key(m_point_objects[i].location.x), key(m_point_objects[i].location.z)}].push_back(i);
|
||||
std::vector<double> nearest(m_point_objects.size(), kAdoptReach);
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (cells.empty() || track == nullptr || track->m_editorremoved || false == editor_track::is_supported(*track))
|
||||
continue;
|
||||
auto const area{area_of(*track)};
|
||||
auto const reach{area.radius + kAdoptReach};
|
||||
for (auto x = key(area.centre.x - reach); x <= key(area.centre.x + reach); ++x)
|
||||
for (auto z = key(area.centre.z - reach); z <= key(area.centre.z + reach); ++z)
|
||||
{
|
||||
auto const found{cells.find({x, z})};
|
||||
if (found == cells.end())
|
||||
continue;
|
||||
for (auto const i : found->second)
|
||||
{
|
||||
auto &object{m_point_objects[i]};
|
||||
if (plan_distance(object.location, area.centre) > area.radius + nearest[i])
|
||||
continue;
|
||||
infra::station station;
|
||||
bool interior;
|
||||
auto const distance{infra::project(*track, object.location, station, interior)};
|
||||
if (distance < nearest[i] && beside(station, object.location, interior))
|
||||
{
|
||||
nearest[i] = distance;
|
||||
object.nearest = track;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
auto const area{area_of(Track)};
|
||||
for (auto const &object : m_point_objects)
|
||||
{
|
||||
if (plan_distance(object.location, area.centre) > area.radius + kAdoptReach)
|
||||
continue;
|
||||
infra::binding binding;
|
||||
binding.type = infra::kind::include;
|
||||
binding.include = object.include;
|
||||
if (infra_find(binding) != nullptr)
|
||||
continue;
|
||||
// of the line of the track: read by it or by a path of its run, else nearest to it; not one of the track alongside
|
||||
if (object.reader != nullptr && object.reader != &Track)
|
||||
{
|
||||
auto const spans{editor_track::run_route(*object.reader, 250.0).spans};
|
||||
if (std::none_of(spans.begin(), spans.end(), [&](auto const &Span) { return Span.track == &Track; }))
|
||||
continue;
|
||||
}
|
||||
if (object.reader == nullptr && object.nearest != &Track)
|
||||
continue;
|
||||
infra::station station;
|
||||
bool interior;
|
||||
if (infra::project(Track, object.location, station, interior) > kAdoptReach || false == beside(station, object.location, interior))
|
||||
continue;
|
||||
std::vector<glm::dvec3> points;
|
||||
std::optional<double> yaw;
|
||||
if (false == object_points(binding, points, yaw) || points.empty())
|
||||
continue;
|
||||
binding.group = object.group;
|
||||
binding.anchors.push_back(infra::make_anchor(station, points.front()));
|
||||
binding.turns = yaw.has_value() && object.group != infra::category::logic;
|
||||
if (binding.turns)
|
||||
binding.yaw = turn_of(binding, *yaw);
|
||||
binding.label = object_label(binding);
|
||||
binding.adopted = true;
|
||||
binding.measured = points.front();
|
||||
m_bindings.push_back(std::move(binding));
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_captured(TTrack const &Track)
|
||||
{
|
||||
sweeps_captured(Track);
|
||||
@@ -1160,6 +1305,8 @@ void editor_mode::track_captured(TTrack const &Track)
|
||||
if (m_infra_suspended || false == m_infra.follow)
|
||||
return;
|
||||
infra_load();
|
||||
// the signals and the like of the track follow it, staying where they stand along it
|
||||
infra_adopt(const_cast<TTrack &>(Track));
|
||||
if (m_bindings.empty())
|
||||
return;
|
||||
Live.refresh();
|
||||
@@ -1188,6 +1335,8 @@ void editor_mode::track_captured(TTrack const &Track)
|
||||
if (binding.turns && yaw.has_value() && binding.anchors.front().at.track == &Track)
|
||||
binding.yaw = turn_of(binding, *yaw);
|
||||
}
|
||||
if (binding.adopted && false == points.empty())
|
||||
binding.measured = points.front();
|
||||
infra_buffer(binding, true);
|
||||
}
|
||||
}
|
||||
@@ -1223,6 +1372,7 @@ void editor_mode::tracks_committed(std::vector<TTrack *> const &Tracks)
|
||||
// only a path running where the old one did, not the one alongside
|
||||
return nearest_beside(*everywhere, Foot, 1.0, Station);
|
||||
};
|
||||
std::vector<scene::instance_handle> dropped;
|
||||
for (auto &binding : m_bindings)
|
||||
{
|
||||
auto const affected{std::any_of(binding.anchors.begin(), binding.anchors.end(), [&](infra::anchor const &Anchor) {
|
||||
@@ -1230,6 +1380,14 @@ void editor_mode::tracks_committed(std::vector<TTrack *> const &Tracks)
|
||||
})};
|
||||
if (false == affected || false == alive(binding))
|
||||
continue;
|
||||
std::vector<glm::dvec3> standing;
|
||||
std::optional<double> facing;
|
||||
if (binding.adopted && (false == object_points(binding, standing, facing) || standing.empty() || glm::distance(standing.front(), binding.measured) > 0.01))
|
||||
{
|
||||
// moved by another tool while its track was being changed: it stays where that put it, bound again with the next change
|
||||
dropped.push_back(binding.include);
|
||||
continue;
|
||||
}
|
||||
auto moved{binding};
|
||||
auto lost{false};
|
||||
for (auto &anchor : moved.anchors)
|
||||
@@ -1256,23 +1414,53 @@ void editor_mode::tracks_committed(std::vector<TTrack *> const &Tracks)
|
||||
}
|
||||
auto const offset{anchor.offset};
|
||||
auto const height{anchor.height};
|
||||
// a path taking the object over may run the other way: the object stays on its side, turned as it was
|
||||
auto const before{plan_of(anchor.end - anchor.start)};
|
||||
anchor = infra::make_anchor(station, anchor.foot);
|
||||
anchor.offset = offset;
|
||||
auto const reversed{glm::length(before) > 1e-6 && glm::dot(before, infra::frame_at(anchor.at).forward) < 0.0};
|
||||
anchor.offset = reversed ? -offset : offset;
|
||||
anchor.height = height;
|
||||
if (reversed && &anchor == &moved.anchors.front())
|
||||
moved.yaw = clamp_circular(moved.yaw + 180.0);
|
||||
}
|
||||
if (lost)
|
||||
{
|
||||
binding.lost = true;
|
||||
if (binding.adopted)
|
||||
dropped.push_back(binding.include);
|
||||
else
|
||||
binding.lost = true;
|
||||
continue;
|
||||
}
|
||||
if (binding.adopted)
|
||||
{
|
||||
// where nothing moved, the directive stays as it is written
|
||||
auto const there{infra::place(moved.anchors.front())};
|
||||
auto const turned{facing.has_value() && moved.turns ? std::abs(clamp_circular(infra::heading(infra::frame_at(moved.anchors.front().at).forward) + moved.yaw - *facing + 180.0) - 180.0) : 0.0};
|
||||
if (glm::distance(there, standing.front()) < 0.005 && turned < 0.01)
|
||||
{
|
||||
moved.lost = false;
|
||||
binding = std::move(moved);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
infra_buffer(binding, false);
|
||||
moved.lost = false;
|
||||
binding = std::move(moved);
|
||||
infra_move(binding);
|
||||
m_infra_carried.emplace(binding.node, binding.include);
|
||||
if (binding.adopted)
|
||||
{
|
||||
std::vector<glm::dvec3> points;
|
||||
std::optional<double> yaw;
|
||||
if (object_points(binding, points, yaw) && false == points.empty())
|
||||
binding.measured = points.front();
|
||||
}
|
||||
for (auto &entry : m_infra_buffer)
|
||||
if (entry.saved.same(binding))
|
||||
entry.moved = true;
|
||||
}
|
||||
if (false == dropped.empty())
|
||||
m_bindings.erase(std::remove_if(m_bindings.begin(), m_bindings.end(), [&](infra::binding const &Binding) { return Binding.adopted && contains(dropped, Binding.include); }), m_bindings.end());
|
||||
}
|
||||
|
||||
void editor_mode::track_retired(TTrack &Track)
|
||||
@@ -1360,6 +1548,9 @@ void editor_mode::infra_attach(EditorSnapshot &Snapshot)
|
||||
auto const involved = [&](infra::object_state const &State) {
|
||||
if (State.moved)
|
||||
return true;
|
||||
// one the editor bound for the change which didn't move: its own changes, if any, are taken back by the tool which made them
|
||||
if (State.saved.adopted)
|
||||
return false;
|
||||
for (auto const &anchor : State.saved.anchors)
|
||||
{
|
||||
if (contains(Snapshot.created, anchor.at.track) || contains(Snapshot.removed, anchor.at.track))
|
||||
@@ -1394,7 +1585,7 @@ void editor_mode::render_infra_body()
|
||||
"models and includes turn with the path and stay upright. The move is a part of the undo step of the change."));
|
||||
ImGui::SameLine();
|
||||
ImGui::Checkbox(STR_C("Show in 3D"), &state.show);
|
||||
ImGui::Text(STR_C("Objects bound in the scenery: %d"), static_cast<int>(m_bindings.size()));
|
||||
ImGui::Text(STR_C("Objects bound in the scenery: %d"), infra_bound_count());
|
||||
if (false == m_bindings_unresolved.empty())
|
||||
{
|
||||
ImGui::SameLine();
|
||||
@@ -1612,9 +1803,9 @@ void editor_mode::render_infra_bound()
|
||||
std::unordered_set<TTrack const *> const tracks(scope.begin(), scope.end());
|
||||
std::vector<std::size_t> along;
|
||||
for (std::size_t i = 0; i < m_bindings.size(); ++i)
|
||||
if (std::any_of(m_bindings[i].anchors.begin(), m_bindings[i].anchors.end(), [&](infra::anchor const &Anchor) { return tracks.count(Anchor.at.track) > 0; }))
|
||||
if (false == m_bindings[i].adopted && std::any_of(m_bindings[i].anchors.begin(), m_bindings[i].anchors.end(), [&](infra::anchor const &Anchor) { return tracks.count(Anchor.at.track) > 0; }))
|
||||
along.push_back(i);
|
||||
ImGui::Text(STR_C("%d of %d"), static_cast<int>(along.size()), static_cast<int>(m_bindings.size()));
|
||||
ImGui::Text(STR_C("%d of %d"), static_cast<int>(along.size()), infra_bound_count());
|
||||
if (false == along.empty())
|
||||
{
|
||||
ImGui::SameLine();
|
||||
@@ -1678,7 +1869,8 @@ void editor_mode::draw_infra_overlay() const
|
||||
drawlist->AddCircleFilled(screen, Size, Colour);
|
||||
};
|
||||
for (auto const &binding : m_bindings)
|
||||
draw(binding, binding.lost ? overlay_color::invalid : IM_COL32(90, 230, 110, 220), 3.5f);
|
||||
if (false == binding.adopted)
|
||||
draw(binding, binding.lost ? overlay_color::invalid : IM_COL32(90, 230, 110, 220), 3.5f);
|
||||
auto const object = [&](infra_candidate const &Candidate) {
|
||||
std::vector<glm::dvec3> points;
|
||||
std::optional<double> yaw;
|
||||
|
||||
@@ -590,6 +590,7 @@ void editor_mode::route_lay()
|
||||
{
|
||||
auto &route{m_route};
|
||||
route.status.clear();
|
||||
m_infra_carried.clear();
|
||||
if (false == route.result.valid || route.chain.tracks.empty())
|
||||
return;
|
||||
editor_track::chain chain;
|
||||
@@ -760,9 +761,10 @@ void editor_mode::route_lay()
|
||||
std::vector<TTrack *> relaid;
|
||||
if (chain.switches.empty())
|
||||
{
|
||||
auto pieces{alignment::pieces(route.result, route.design, chain.tracks.size())};
|
||||
std::vector<std::size_t> intervals;
|
||||
auto pieces{alignment::pieces_at(route.result, route.design, chain.tracks.size(), editor_track::chain_joints(chain), intervals)};
|
||||
editor_track::keep_heights(chain, pieces);
|
||||
relaid = editor_track::relay(chain, pieces, &to);
|
||||
relaid = editor_track::relay(chain, pieces, &to, &intervals);
|
||||
}
|
||||
else if (false == route_relay_through_switches(chain, states, relaid, to))
|
||||
{
|
||||
@@ -793,6 +795,7 @@ void editor_mode::route_lay()
|
||||
route.status = added > 0 ? format(STR_P("Re-laid %zu path, added %zu", "Re-laid %zu paths, added %zu", chain.tracks.size()), chain.tracks.size(), added) : format(STR_P("Re-laid %zu path", "Re-laid %zu paths", chain.tracks.size()), chain.tracks.size());
|
||||
if (startextension > 0.0 || endextension > 0.0)
|
||||
route.status += format(STR_C(", the curve took over %.2f / %.2f m of the adjoining straights"), startextension, endextension);
|
||||
route.status += infra_carried_note();
|
||||
// the crossovers taken out before which fit the lines now
|
||||
if (false == route.relaying && false == route.crossovers_out.empty())
|
||||
{
|
||||
@@ -844,14 +847,14 @@ bool editor_mode::route_relay_through_switches(editor_track::chain const &Chain,
|
||||
}
|
||||
auto const oldlength{before};
|
||||
std::vector<placement> placements;
|
||||
std::vector<double> breaks;
|
||||
std::vector<double> ends; // of the switches along the new line
|
||||
for (std::size_t k = 0; k < Chain.switches.size(); ++k)
|
||||
{
|
||||
auto const &passage{Chain.switches[k]};
|
||||
auto const &main{passage.track->m_paths.front()};
|
||||
auto const length{bezier{main}.plan_length()};
|
||||
auto const from{starts[k] + length * 0.5 < oldlength * 0.5 ? starts[k] : result.length - (oldlength - starts[k])};
|
||||
if (from < 0.0 || from + length > result.length || (false == breaks.empty() && from < breaks.back() - 0.01))
|
||||
if (from < 0.0 || from + length > result.length || (false == ends.empty() && from < ends.back() - 0.01))
|
||||
{
|
||||
route.error = format(STR_C("The switch %s doesn't fit on the designed line"), name(passage.track).c_str());
|
||||
return false;
|
||||
@@ -874,22 +877,50 @@ bool editor_mode::route_relay_through_switches(editor_track::chain const &Chain,
|
||||
return false;
|
||||
}
|
||||
placements.push_back({passage.track, from, length, entry, angle});
|
||||
breaks.push_back(from);
|
||||
breaks.push_back(from + length);
|
||||
ends.push_back(from);
|
||||
ends.push_back(from + length);
|
||||
}
|
||||
// the regular parts between the switches, one interval each, the switches taking the ones between
|
||||
// the regular parts between the switches, cut at the joints of their paths set square onto the new line where they follow one
|
||||
// another along it, else one interval each; the switches taking the ones between
|
||||
auto const parts{editor_track::chain_parts(Chain)};
|
||||
std::vector<double> breaks;
|
||||
std::vector<std::size_t> counts;
|
||||
std::vector<int> partof;
|
||||
std::vector<std::pair<int, int>> owners; // of each interval: the part, -1 for none, and its path, -1 for any
|
||||
std::size_t part{0};
|
||||
for (std::size_t k = 0; k <= Chain.switches.size(); ++k)
|
||||
{
|
||||
auto const first{k == 0 ? std::size_t{0} : Chain.switches[k - 1].before};
|
||||
auto const last{k == Chain.switches.size() ? Chain.tracks.size() : Chain.switches[k].before};
|
||||
counts.push_back(last - first);
|
||||
partof.push_back(last > first ? static_cast<int>(part++) : -1);
|
||||
if (last > first && part < parts.size())
|
||||
{
|
||||
auto const inner{alignment::breaks_at(result, editor_track::chain_joints(parts[part]), k == 0 ? 0.0 : ends[2 * k - 1], k == Chain.switches.size() ? result.length : ends[2 * k])};
|
||||
if (last - first > 1 && inner.size() + 1 == last - first)
|
||||
for (std::size_t j = 0; j < last - first; ++j)
|
||||
{
|
||||
if (j > 0)
|
||||
breaks.push_back(inner[j - 1]);
|
||||
counts.push_back(1);
|
||||
owners.emplace_back(static_cast<int>(part), static_cast<int>(j));
|
||||
}
|
||||
else
|
||||
{
|
||||
counts.push_back(last - first);
|
||||
owners.emplace_back(static_cast<int>(part), -1);
|
||||
}
|
||||
++part;
|
||||
}
|
||||
else
|
||||
{
|
||||
counts.push_back(last - first);
|
||||
owners.emplace_back(-1, -1);
|
||||
}
|
||||
if (k < Chain.switches.size())
|
||||
{
|
||||
breaks.push_back(ends[2 * k]);
|
||||
breaks.push_back(ends[2 * k + 1]);
|
||||
counts.push_back(1);
|
||||
owners.emplace_back(-1, -1);
|
||||
}
|
||||
}
|
||||
if (part != parts.size())
|
||||
{
|
||||
@@ -899,9 +930,15 @@ bool editor_mode::route_relay_through_switches(editor_track::chain const &Chain,
|
||||
std::vector<std::size_t> intervals;
|
||||
auto const pieces{alignment::pieces(result, route.design, 0, breaks, counts, &intervals)};
|
||||
std::vector<std::vector<segment_data>> groups(parts.size());
|
||||
for (std::size_t i = 0; i < pieces.size(); ++i)
|
||||
if (intervals[i] % 2 == 0 && partof[intervals[i] / 2] >= 0)
|
||||
groups[partof[intervals[i] / 2]].push_back(pieces[i]);
|
||||
std::vector<std::vector<std::size_t>> paths(parts.size()); // of each piece of a part, its path, where its joints cut the line
|
||||
for (std::size_t i = 0; i < pieces.size() && i < intervals.size(); ++i)
|
||||
if (intervals[i] < owners.size() && owners[intervals[i]].first >= 0)
|
||||
{
|
||||
auto const &[owner, path]{owners[intervals[i]]};
|
||||
groups[owner].push_back(pieces[i]);
|
||||
if (path >= 0)
|
||||
paths[owner].push_back(static_cast<std::size_t>(path));
|
||||
}
|
||||
for (std::size_t i = 0; i < parts.size(); ++i)
|
||||
if (groups[i].size() < parts[i].tracks.size())
|
||||
{
|
||||
@@ -938,7 +975,7 @@ bool editor_mode::route_relay_through_switches(editor_track::chain const &Chain,
|
||||
auto group{groups[i]};
|
||||
editor_track::keep_heights(parts[i], group);
|
||||
TTrack *last{nullptr};
|
||||
auto const added{editor_track::relay(parts[i], group, &last)};
|
||||
auto const added{editor_track::relay(parts[i], group, &last, paths[i].size() == group.size() ? &paths[i] : nullptr)};
|
||||
if (last != nullptr && parts[i].tracks.back() == Chain.tracks.back())
|
||||
Last = last;
|
||||
Created.insert(Created.end(), added.begin(), added.end());
|
||||
|
||||
@@ -786,6 +786,7 @@ void editor_mode::straight_reshape(editor_track::straight const &Line, double co
|
||||
{
|
||||
auto &state{m_straights};
|
||||
state.status.clear();
|
||||
m_infra_carried.clear();
|
||||
struct span
|
||||
{
|
||||
TTrack *track;
|
||||
@@ -973,9 +974,10 @@ void editor_mode::straight_reshape(editor_track::straight const &Line, double co
|
||||
state.status = refused(m_route.result.errors.empty() ? STR_C("The curve can't be fitted") : m_route.result.errors.front());
|
||||
return;
|
||||
}
|
||||
auto pieces{alignment::pieces(m_route.result, m_route.design, m_route.chain.tracks.size())};
|
||||
std::vector<std::size_t> intervals;
|
||||
auto pieces{alignment::pieces_at(m_route.result, m_route.design, m_route.chain.tracks.size(), editor_track::chain_joints(m_route.chain), intervals)};
|
||||
editor_track::keep_heights(m_route.chain, pieces);
|
||||
auto created{editor_track::relay(m_route.chain, pieces, &m_route.to)};
|
||||
auto created{editor_track::relay(m_route.chain, pieces, &m_route.to, &intervals)};
|
||||
editor_track::commit(tracks);
|
||||
for (auto const &entry : splitstates)
|
||||
{
|
||||
@@ -996,7 +998,7 @@ void editor_mode::straight_reshape(editor_track::straight const &Line, double co
|
||||
std::string error;
|
||||
editor_track::find_chain(m_route.from, m_route.to, m_route.chain, error, true);
|
||||
straight_refresh();
|
||||
state.status = STR_C("Done: click a curve to adjust it; Ctrl+Z takes it back");
|
||||
state.status = STR_C("Done: click a curve to adjust it; Ctrl+Z takes it back") + infra_carried_note();
|
||||
}
|
||||
|
||||
double editor_mode::straight_tool_radius(editor_track::straight const &Line) const
|
||||
|
||||
@@ -2128,6 +2128,29 @@ trace_fit fit_trace(design const &Design, std::size_t const Vertex, std::vector<
|
||||
return best;
|
||||
}
|
||||
|
||||
std::vector<double> breaks_at(result const &Result, std::vector<glm::dvec2> const &Points, double const From, double const To)
|
||||
{
|
||||
double constexpr apart{0.2};
|
||||
std::vector<double> breaks;
|
||||
auto previous{From};
|
||||
for (auto const &at : project(Result, Points))
|
||||
{
|
||||
if (at.chainage < previous + apart || at.chainage > To - apart)
|
||||
return {};
|
||||
breaks.push_back(previous = at.chainage);
|
||||
}
|
||||
return breaks;
|
||||
}
|
||||
|
||||
std::vector<segment_data> pieces_at(result const &Result, design const &Design, std::size_t const Count, std::vector<glm::dvec2> const &Joints, std::vector<std::size_t> &Intervals)
|
||||
{
|
||||
Intervals.clear();
|
||||
auto const breaks{breaks_at(Result, Joints, 0.0, Result.length)};
|
||||
if (breaks.empty() || breaks.size() != Joints.size())
|
||||
return pieces(Result, Design, Count);
|
||||
return pieces(Result, Design, Count, breaks, std::vector<std::size_t>(breaks.size() + 1, 1), &Intervals);
|
||||
}
|
||||
|
||||
std::size_t minimum_pieces(result const &Result, design const &Design)
|
||||
{
|
||||
std::size_t count{0};
|
||||
|
||||
@@ -217,6 +217,13 @@ std::size_t minimum_pieces(result const &Result, design const &Design);
|
||||
// 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);
|
||||
// the points of the plan set square onto the line, the chainages of them in their order between From and To; empty where they
|
||||
// don't follow one another along it
|
||||
std::vector<double> breaks_at(result const &Result, std::vector<glm::dvec2> const &Points, double const From, double const To);
|
||||
// the pieces of the line ending at the joints of the old paths set square onto it, so that each old path keeps its stretch of the
|
||||
// ground: Intervals receives the old path of each piece, or stays empty where the joints don't follow one another along the line
|
||||
// and the pieces are cut as by pieces() alone
|
||||
std::vector<segment_data> pieces_at(result const &Result, design const &Design, std::size_t const Count, std::vector<glm::dvec2> const &Joints, std::vector<std::size_t> &Intervals);
|
||||
|
||||
struct join_end
|
||||
{
|
||||
|
||||
@@ -117,6 +117,9 @@ struct binding
|
||||
bool turns{false}; // the object has a rotation which follows the path
|
||||
std::string label;
|
||||
bool lost{false}; // none of the paths around could take the object over
|
||||
// bound by the editor itself when its track was taken for a change, as a signal its path reads: not kept in the scenery
|
||||
bool adopted{false};
|
||||
glm::dvec3 measured{0.0}; // where an adopted object stood when its track was taken
|
||||
|
||||
bool same(binding const &Other) const
|
||||
{
|
||||
|
||||
@@ -3346,18 +3346,22 @@ void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start,
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<TTrack *> editor_track::relay(chain const &Chain, std::vector<segment_data> const &Pieces, TTrack **Last)
|
||||
std::vector<TTrack *> editor_track::relay(chain const &Chain, std::vector<segment_data> const &Pieces, TTrack **Last, std::vector<std::size_t> const *Intervals)
|
||||
{
|
||||
std::vector<TTrack *> created;
|
||||
auto const count{Chain.tracks.size()};
|
||||
if (count == 0 || Pieces.size() < count)
|
||||
return created;
|
||||
|
||||
// each path of the chain keeps the stretch of the ground it had, where the pieces were cut at its joints: its events and its
|
||||
// track circuit stay where they were, not handed on along the line
|
||||
auto const along{Intervals != nullptr && Intervals->size() == Pieces.size() && Intervals->front() == 0 && Intervals->back() + 1 == count &&
|
||||
std::adjacent_find(Intervals->begin(), Intervals->end(), [](std::size_t const A, std::size_t const B) { return B < A || B > A + 1; }) == Intervals->end()};
|
||||
std::vector<TTrack *> tracks;
|
||||
std::size_t previous{count};
|
||||
for (std::size_t i = 0; i < Pieces.size(); ++i)
|
||||
{
|
||||
auto const index{i * count / Pieces.size()};
|
||||
auto const index{along ? (*Intervals)[i] : i * count / Pieces.size()};
|
||||
TTrack *track;
|
||||
bool forward;
|
||||
if (index != previous)
|
||||
@@ -3382,6 +3386,18 @@ std::vector<TTrack *> editor_track::relay(chain const &Chain, std::vector<segmen
|
||||
return created;
|
||||
}
|
||||
|
||||
std::vector<glm::dvec2> editor_track::chain_joints(chain const &Chain)
|
||||
{
|
||||
std::vector<glm::dvec2> joints;
|
||||
for (std::size_t i = 0; i + 1 < Chain.tracks.size() && i < Chain.forward.size(); ++i)
|
||||
{
|
||||
auto const &path{Chain.tracks[i]->m_paths.front()};
|
||||
auto const &end{path.points[Chain.forward[i] ? segment_data::point::end : segment_data::point::start]};
|
||||
joints.emplace_back(end.x, end.z);
|
||||
}
|
||||
return joints;
|
||||
}
|
||||
|
||||
TTrack *editor_track::clone(TTrack const &Template, std::string const &Name)
|
||||
{
|
||||
std::ostringstream text;
|
||||
|
||||
@@ -506,7 +506,10 @@ class editor_track
|
||||
static TTrack *create_switch(switch_template const &Template, std::vector<segment_data> const &Paths, TTrack const &Style, std::string const &Name = {});
|
||||
static TTrack *create_turntable(segment_data const &Path, TTrack const *Style, std::string const &Name);
|
||||
static void move_straights(std::vector<straight> const &Lines, std::vector<std::pair<glm::dvec3, glm::dvec3>> const &Ends);
|
||||
static std::vector<TTrack *> relay(chain const &Chain, std::vector<segment_data> const &Pieces, TTrack **Last = nullptr);
|
||||
// Intervals: the path of the chain each piece takes the place of, else they're handed out in turn
|
||||
static std::vector<TTrack *> relay(chain const &Chain, std::vector<segment_data> const &Pieces, TTrack **Last = nullptr, std::vector<std::size_t> const *Intervals = nullptr);
|
||||
// the joints between the paths of the chain in its order, in the plan
|
||||
static std::vector<glm::dvec2> chain_joints(chain const &Chain);
|
||||
static void retire(TTrack &Track);
|
||||
// a retired path back in the scenery, found by its name again; it's committed by the caller
|
||||
static void revive(TTrack &Track);
|
||||
|
||||
@@ -9098,6 +9098,12 @@ msgstr[0] "Ułożono na nowo %zu tor"
|
||||
msgstr[1] "Ułożono na nowo %zu tory"
|
||||
msgstr[2] "Ułożono na nowo %zu torów"
|
||||
|
||||
msgid "\n%zu object by the tracks moved sideways with them"
|
||||
msgid_plural "\n%zu objects by the tracks moved sideways with them"
|
||||
msgstr[0] "\n%zu obiekt przy torach przesunął się z nimi w bok"
|
||||
msgstr[1] "\n%zu obiekty przy torach przesunęły się z nimi w bok"
|
||||
msgstr[2] "\n%zu obiektów przy torach przesunęło się z nimi w bok"
|
||||
|
||||
msgid ", the curve took over %.2f / %.2f m of the adjoining straights"
|
||||
msgstr ", łuk zajął %.2f / %.2f m przyległych prostych"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user