diff --git a/application/editormode.h b/application/editormode.h index 161ee849..842cd07c 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -2372,6 +2372,23 @@ class editor_mode : public application_mode, private editor_track::observer, pri std::vector m_infra_buffer; // states of the objects before the change in progress std::vector> 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 m_point_objects; + std::size_t m_point_objects_revision{static_cast(-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> 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); diff --git a/application/editormodedigitize.cpp b/application/editormodedigitize.cpp index 99ea6d26..75e76f81 100644 --- a/application/editormodedigitize.cpp +++ b/application/editormodedigitize.cpp @@ -389,9 +389,9 @@ std::string editor_mode::digitize_scenery(bool const Write) m_infra.scope = 3; infra_recognize(); report.found = static_cast(m_infra.candidates.size()); - auto const before{m_bindings.size()}; + auto const before{infra_bound_count()}; infra_bind_chosen(); - report.bound = static_cast(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)}; diff --git a/application/editormodeinfra.cpp b/application/editormodeinfra.cpp index 1fef9e8c..abc9f30a 100644 --- a/application/editormodeinfra.cpp +++ b/application/editormodeinfra.cpp @@ -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 bool contains(std::vector const &Items, Type_ const &Item) @@ -803,7 +808,7 @@ void editor_mode::infra_store() { std::vector points; std::optional 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(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 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(std::floor(Value / cell)); }; + std::map, std::vector> 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 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 points; + std::optional 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(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 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 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 const &Tracks) })}; if (false == affected || false == alive(binding)) continue; + std::vector standing; + std::optional 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 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 points; + std::optional 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(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 const tracks(scope.begin(), scope.end()); std::vector 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(along.size()), static_cast(m_bindings.size())); + ImGui::Text(STR_C("%d of %d"), static_cast(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 points; std::optional yaw; diff --git a/application/editormoderoute.cpp b/application/editormoderoute.cpp index 187b55b5..8508b939 100644 --- a/application/editormoderoute.cpp +++ b/application/editormoderoute.cpp @@ -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 relaid; if (chain.switches.empty()) { - auto pieces{alignment::pieces(route.result, route.design, chain.tracks.size())}; + std::vector 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 placements; - std::vector breaks; + std::vector 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 breaks; std::vector counts; - std::vector partof; + std::vector> 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(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(part), static_cast(j)); + } + else + { + counts.push_back(last - first); + owners.emplace_back(static_cast(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 intervals; auto const pieces{alignment::pieces(result, route.design, 0, breaks, counts, &intervals)}; std::vector> 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> 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(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()); diff --git a/application/editormodestraights.cpp b/application/editormodestraights.cpp index 239ced1e..d65d302c 100644 --- a/application/editormodestraights.cpp +++ b/application/editormodestraights.cpp @@ -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 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 diff --git a/editor/editorAlignment.cpp b/editor/editorAlignment.cpp index c8a88a32..b0b501c1 100644 --- a/editor/editorAlignment.cpp +++ b/editor/editorAlignment.cpp @@ -2128,6 +2128,29 @@ trace_fit fit_trace(design const &Design, std::size_t const Vertex, std::vector< return best; } +std::vector breaks_at(result const &Result, std::vector const &Points, double const From, double const To) +{ + double constexpr apart{0.2}; + std::vector 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 pieces_at(result const &Result, design const &Design, std::size_t const Count, std::vector const &Joints, std::vector &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(breaks.size() + 1, 1), &Intervals); +} + std::size_t minimum_pieces(result const &Result, design const &Design) { std::size_t count{0}; diff --git a/editor/editorAlignment.hpp b/editor/editorAlignment.hpp index a96c00b8..c91801af 100644 --- a/editor/editorAlignment.hpp +++ b/editor/editorAlignment.hpp @@ -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 pieces(result const &Result, design const &Design, std::size_t const Count, std::vector const &Breaks = {}, std::vector const &Counts = {}, std::vector *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 breaks_at(result const &Result, std::vector 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 pieces_at(result const &Result, design const &Design, std::size_t const Count, std::vector const &Joints, std::vector &Intervals); struct join_end { diff --git a/editor/editorInfra.hpp b/editor/editorInfra.hpp index a2f34c32..8bd7029d 100644 --- a/editor/editorInfra.hpp +++ b/editor/editorInfra.hpp @@ -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 { diff --git a/editor/editorTrack.cpp b/editor/editorTrack.cpp index 853ed3c7..edd60a4d 100644 --- a/editor/editorTrack.cpp +++ b/editor/editorTrack.cpp @@ -3346,18 +3346,22 @@ void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start, } } -std::vector editor_track::relay(chain const &Chain, std::vector const &Pieces, TTrack **Last) +std::vector editor_track::relay(chain const &Chain, std::vector const &Pieces, TTrack **Last, std::vector const *Intervals) { std::vector 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 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 editor_track::relay(chain const &Chain, std::vector editor_track::chain_joints(chain const &Chain) +{ + std::vector 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; diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index acaf502a..b42a1dd0 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -506,7 +506,10 @@ class editor_track static TTrack *create_switch(switch_template const &Template, std::vector 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 const &Lines, std::vector> const &Ends); - static std::vector relay(chain const &Chain, std::vector 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 relay(chain const &Chain, std::vector const &Pieces, TTrack **Last = nullptr, std::vector const *Intervals = nullptr); + // the joints between the paths of the chain in its order, in the plan + static std::vector 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); diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 2019360c..9abad58e 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -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"