diff --git a/application/editormode.cpp b/application/editormode.cpp index 30e1db72..e18a2520 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -1346,6 +1346,21 @@ bool editor_mode::update() { Global.editor_tracks = (current_work_area() == work_area::tracks); selftest_step(); + // the drives of the switches go to the tips of their blades once, as the templates of the scenery are known + if (false == m_drives_checked) + { + EditorIncludes.start(); + if (EditorIncludes.scanned()) + { + m_drives_checked = true; + if (auto const moved{drives_to_blade_tips()}; moved > 0) + { + auto const text{format(STR_P("The drive of %zu switch is moved to the tips of its blades", "The drives of %zu switches are moved to the tips of their blades", static_cast(moved)), moved)}; + ui()->set_status(text, false); + WriteLog("Editor: " + text, logtype::generic); + } + } + } // after the tool has told its own status if (false == m_orphan_note.empty()) { @@ -1560,6 +1575,7 @@ bool editor_mode::update() draw_track_set(); draw_track_spread(); draw_build_overlay(); + draw_blade_tips(); draw_track_intent(); draw_tape(); m_speed.update(); diff --git a/application/editormode.h b/application/editormode.h index 6aaf58a7..b821f601 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -1714,6 +1714,8 @@ class editor_mode : public application_mode, private editor_track::observer, pri bool extend_snap(editor_track::snap_target &Target) const; void update_build_tools(); void draw_build_overlay() const; + // the joint before the blades of the switch being placed or the one selected and the tips of its blades + void draw_blade_tips() const; // modes of the track editor, one at a time: path is the plain selection using track_tab = editor_track_tab; struct track_mode @@ -2089,6 +2091,12 @@ class editor_mode : public application_mode, private editor_track::observer, pri void apply_switch_drive_choice(TTrack &Switch); void switch_drive_captured(TTrack const &Track); void switch_drives_committed(std::vector const &Tracks); + // the drives of the switches deleted go with them, in the step of the undo history the deletion made last + void drives_deleted(std::vector const &Switches); + // the drives of all the switches of the scenery set at the tips of their blades, the ones the earlier versions of the editor set at the + // joint before them too: once, as the scenery is opened, with no step of the undo history; how many moved + std::size_t drives_to_blade_tips(); + bool m_drives_checked{false}; scene::layer_handle output_layer(std::string const &Part) override; // places them in the active layer or the given one, as one step of the undo. returns: how many were placed std::size_t place_templates(std::vector const &Items, std::string &Error, scene::layer_handle Layer = scene::layer_handle{}) override; diff --git a/application/editormodeselftestswitch.cpp b/application/editormodeselftestswitch.cpp index 25894069..0f2d5c99 100644 --- a/application/editormodeselftestswitch.cpp +++ b/application/editormodeselftestswitch.cpp @@ -50,6 +50,28 @@ http://mozilla.org/MPL/2.0/. editor_mode::selftest_result editor_mode::selftest_switch(std::string const &command, std::istringstream &words) { + // each switch: the tips of its blades and its drive from the joint before them; drivetips moves the drives there first + if (command == "bladetips" || command == "drivetips") + { + if (command == "drivetips") + WriteLog(format("SELFTEST drivetips: %zu moved", drives_to_blade_tips())); + for (auto *track : simulation::Paths.sequence()) + { + if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->m_paths.size() < 2) + continue; + auto const start{track->m_paths[0].points[segment_data::point::start]}; + std::string text{format("SELFTEST drive of %s: blades from %.3f m", track->name().c_str(), editor_track::blade_tip_of(*track))}; + for (int path = 0; path < 2; ++path) + if (auto const &shape{track->blade(path)}; shape.valid) + text += format(", blade %d %.3f - %.3f m", path, shape.tip, shape.heel); + if (auto const found{find_switch_drive(*track)}) + text += format(", drive %.3f m from the joint", geometry::plan_distance(found->standing.location, start)); + else + text += ", no drive"; + WriteLog(text); + } + return selftest_result::handled; + } if (command == "curvedswitch") { std::string name; diff --git a/application/editormodeswitchdrive.cpp b/application/editormodeswitchdrive.cpp index 9b19c419..37dd0d01 100644 --- a/application/editormodeswitchdrive.cpp +++ b/application/editormodeswitchdrive.cpp @@ -292,6 +292,41 @@ bool editor_mode::replace_switch_drive(TTrack &Switch, int const Family, bool co return true; } +void editor_mode::drives_deleted(std::vector const &Switches) +{ + if (m_history.empty()) + return; + for (auto const *track : Switches) + { + if (track == nullptr || track->eType != tt_Switch) + continue; + auto const found{find_switch_drive(*track)}; + if (false == found.has_value() || false == scene::Layers.removable(found->standing.instance) || scene::Layers.instance(found->standing.instance).removed) + continue; + scene::Layers.removed(found->standing.instance, true); + m_history.back().instances.push_back(found->standing.instance); + } +} + +std::size_t editor_mode::drives_to_blade_tips() +{ + // the drives the earlier versions of the editor set at the joint before the blades among them + std::vector switches; + std::vector> before; + for (auto *track : simulation::Paths.sequence()) + { + if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch) + continue; + if (auto const found{find_switch_drive(*track)}; found.has_value() && scene::Layers.removable(found->standing.instance)) + { + switches.push_back(track); + before.emplace_back(found->standing.instance, scene::Layers.directive(found->standing.instance)); + } + } + switch_drives_committed(switches); + return static_cast(std::count_if(before.begin(), before.end(), [](auto const &Before) { return scene::Layers.directive(Before.first) != Before.second; })); +} + bool editor_mode::render_switch_drive_choice() { auto &tool{m_switch}; diff --git a/application/editormodetrack.cpp b/application/editormodetrack.cpp index c06860f9..3f02fe31 100644 --- a/application/editormodetrack.cpp +++ b/application/editormodetrack.cpp @@ -1641,6 +1641,7 @@ void editor_mode::delete_selected_track() if (auto const note{designs_trimmed({set->first, set->link, set->second})}; false == note.empty()) ui()->set_status(note, false); push_track_snapshot({}, created, {set->first, set->link, set->second}); + drives_deleted({set->first, set->second}); if (std::any_of(m_route.chain.tracks.begin(), m_route.chain.tracks.end(), [&](TTrack const *Each) { return Each == set->first || Each == set->link || Each == set->second; })) m_route = {}; m_node = nullptr; @@ -1666,6 +1667,7 @@ void editor_mode::delete_selected_track() if (auto const note{designs_trimmed({track})}; false == note.empty()) ui()->set_status(note, false); push_track_snapshot({}, created, {track}); + drives_deleted({track}); // the model and the events of a turntable can't stay without its track if (track->eType == tt_Table) for (auto const instance : turntable_includes(*track)) @@ -2264,6 +2266,53 @@ void editor_mode::draw_track_hints() foreground->AddText(at, IM_COL32(255, 255, 255, 255), readout.c_str()); } +void editor_mode::draw_blade_tips() const +{ + segment_data main; + double tip{0.0}; + if (m_switch.placing && m_switch.connection < 0) + { + auto const preview{switch_preview()}; + if (preview.size() < 2) + return; + main = preview[0]; + tip = editor_track::blade_tip_of(preview[1]); + } + else if (auto const *selected{selected_track()}; selected != nullptr && selected->eType == tt_Switch && selected->m_paths.size() >= 2 && false == selected->m_editorremoved) + { + main = selected->m_paths[0]; + tip = editor_track::blade_tip_of(*selected); + } + else + return; + // on the photo the tips of the blades are seen where the switch seems to begin, the joint before them isn't + screen_projection const projection; + auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())}; + geometry::bezier const curve{main}; + auto const mark = [&](double const Distance, ImU32 const Colour, float const Width, char const *Label, int const Side) { + auto const t{curve.parameter(Distance)}; + auto const at{curve.point(t)}; + auto direction{plan_of(curve.first(t))}; + if (glm::length(direction) < 1e-9) + return; + direction = glm::normalize(direction); + glm::dvec3 const across{-direction.y * 1.6, 0.0, direction.x * 1.6}; + projection.line(drawlist, at - across, at + across, IM_COL32(0, 0, 0, 170), Width + 3.0f); + projection.line(drawlist, at - across, at + across, Colour, Width); + ImVec2 screen; + if (projection.project(at + across * static_cast(Side), screen)) + { + auto const size{ImGui::CalcTextSize(Label)}; + ImVec2 const corner{Side > 0 ? screen.x + 6.0f : screen.x - 6.0f - size.x, screen.y - 0.5f * size.y}; + drawlist->AddRectFilled({corner.x - 3.0f, corner.y - 1.0f}, {corner.x + size.x + 3.0f, corner.y + size.y + 1.0f}, IM_COL32(0, 0, 0, 160), 3.0f); + drawlist->AddText(corner, Colour, Label); + } + }; + mark(0.0, IM_COL32(220, 220, 220, 230), 2.0f, STR_C("joint before the blades"), -1); + if (tip > 0.05) + mark(tip, IM_COL32(255, 170, 40, 255), 3.0f, STR_C("tips of the blades"), 1); +} + void editor_mode::draw_build_overlay() const { screen_projection const projection; diff --git a/application/editormodetrackux.cpp b/application/editormodetrackux.cpp index bcba0273..737d86e3 100644 --- a/application/editormodetrackux.cpp +++ b/application/editormodetrackux.cpp @@ -1831,6 +1831,7 @@ void editor_mode::track_set_delete() } auto const designs{designs_trimmed(removed)}; push_track_snapshot({}, {}, removed); + drives_deleted(removed); m_track_set.clear(); m_node = nullptr; ui()->set_node(nullptr); diff --git a/editor/editorTrack.hpp b/editor/editorTrack.hpp index 439a9cf0..c2e6e530 100644 --- a/editor/editorTrack.hpp +++ b/editor/editorTrack.hpp @@ -208,7 +208,12 @@ class editor_track glm::dvec2 tilt{0.0}; bool rightswitch{false}; }; + // the drive of the switch by the tips of its blades, where the rods throw them over static std::optional drive_spot_of(TTrack const &Switch); + // how far along the main track of the switch the tips of its blades lie, where the pre-blade stretch from the joint ends: as the + // blades are shaped, else where the diverging track is broken at the tip of a blade planed back from the start; 0 at the start + static double blade_tip_of(TTrack const &Switch); + static double blade_tip_of(segment_data const &Diverging); struct straight_axis { diff --git a/editor/editorTrackShape.cpp b/editor/editorTrackShape.cpp index dd626bdb..b56d27ae 100644 --- a/editor/editorTrackShape.cpp +++ b/editor/editorTrackShape.cpp @@ -299,6 +299,26 @@ glm::dvec3 editor_track::control_vector(TTrack const &Track, point_ref const &Co return point_position(Track, Control) - point_position(Track, end_of(Control)); } +double editor_track::blade_tip_of(segment_data const &Diverging) +{ + // the tip of a planed blade lies some metres from the start at most, a longer first piece is a part of the curve + if (Diverging.pieces.size() < 2) + return 0.0; + auto const length{bezier{Diverging.pieces.front()}.plan_length()}; + return length < 5.0 ? length : 0.0; +} + +double editor_track::blade_tip_of(TTrack const &Switch) +{ + if (Switch.eType != tt_Switch || Switch.m_paths.size() < 2) + return 0.0; + double tip{0.0}; + for (int path = 0; path < 2; ++path) + if (auto const &shape{Switch.blade(path)}; shape.valid && shape.tip > 0.0) + tip = tip > 0.0 ? std::min(tip, shape.tip) : shape.tip; + return tip > 0.0 ? tip : blade_tip_of(Switch.m_paths[1]); +} + std::optional editor_track::drive_spot_of(TTrack const &Switch) { if (Switch.eType != tt_Switch || Switch.m_paths.size() < 2) @@ -306,8 +326,10 @@ std::optional editor_track::drive_spot_of(TTrack const auto const &main{Switch.m_paths[0]}; auto const &diverging{Switch.m_paths[1]}; auto const start{main.points[segment_data::point::start]}; - auto const &control{main.points[segment_data::point::control1]}; - auto const along{control != glm::dvec3{} ? control : main.points[segment_data::point::end] - start}; + // by the tips of the blades, not at the joint before them + bezier const curve{main}; + auto const at{curve.parameter(blade_tip_of(Switch))}; + auto const along{curve.first(at)}; auto heading{plan_of(along)}; if (glm::length(heading) < 1e-9) return std::nullopt; @@ -315,11 +337,11 @@ std::optional editor_track::drive_spot_of(TTrack const glm::dvec2 const right{-heading.y, heading.x}; drive_spot spot; spot.rightswitch = glm::dot(plan_of(diverging.points[segment_data::point::end] - start), right) > 0.0; - spot.location = start; + spot.location = curve.point(at); auto const yaw{glm::degrees(std::atan2(heading.x, heading.y))}; spot.yaw = yaw < 0.0 ? yaw + 360.0 : yaw; auto const run{glm::length(plan_of(along))}; - auto const roll{static_cast(main.rolls[0])}; + auto const roll{glm::mix(static_cast(main.rolls[0]), static_cast(main.rolls[1]), at)}; spot.tilt = {-glm::degrees(std::atan(along.y / run)), roll}; if (Global.bRollFix) spot.location.y += 0.75 * std::abs(std::sin(glm::radians(roll))); diff --git a/lang/editor_pl.po b/lang/editor_pl.po index 08a111d7..bab22d1b 100644 --- a/lang/editor_pl.po +++ b/lang/editor_pl.po @@ -12303,3 +12303,15 @@ msgid_plural "The designs kept for the lines are cut down to the tracks left alo msgstr[0] "Projekt linii zapisany w scenerii przycięto do torów, które przy nim zostały" msgstr[1] "Projekty linii zapisane w scenerii przycięto do torów, które przy nich zostały" msgstr[2] "Projekty linii zapisane w scenerii przycięto do torów, które przy nich zostały" + +msgid "joint before the blades" +msgstr "styk przediglicowy" + +msgid "tips of the blades" +msgstr "ostrza iglic" + +msgid "The drive of %zu switch is moved to the tips of its blades" +msgid_plural "The drives of %zu switches are moved to the tips of their blades" +msgstr[0] "Napęd %zu rozjazdu przestawiony na ostrza iglic" +msgstr[1] "Napędy %zu rozjazdów przestawione na ostrza iglic" +msgstr[2] "Napędy %zu rozjazdów przestawione na ostrza iglic"