17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 05:39:29 +01:00

feat(editor): the hectometre posts go by the kilometrage of the line where the Kilometrage tool sets it, its jumps too, a post at a jump of the km on from it or of the km it is reached at, the km given at the point still there as a choice; the posts standing go with the place of their number nearest to them

This commit is contained in:
maj00r
2026-10-22 14:33:18 +02:00
parent 7cc8e5bb9a
commit 435b80eb55
6 changed files with 240 additions and 101 deletions

View File

@@ -163,51 +163,22 @@ void km_tool::refresh()
auto const high{axis->anchors.back().chainage + 1.0};
for (auto const chainage : axis->chainage)
entry.guessed.push_back(chainage < low || chainage > high ? 1 : 0);
std::vector<double> jumps;
for (auto const &anchor : axis->anchors)
{
if (std::abs(anchor.after - anchor.before) < 1e-6)
continue;
jumps.push_back(anchor.chainage);
auto const way{kilometrage::direction_on(*axis, anchor.chainage)};
entry.ticks.push_back({kilometrage::point_on(*axis, anchor.chainage), {-way.y, 0.0, way.x}, 0, true});
}
// a tick at each hectometre the km pass along the axis, none at a jump
auto const hectometres_along = [&](std::vector<glm::dvec3> const &Points, std::vector<double> const &Km, std::vector<double> const &Chainage, std::vector<tick> &Ticks) {
for (std::size_t i = 1; i < Points.size(); ++i)
if (std::abs(anchor.after - anchor.before) >= 1e-6)
{
auto const from{std::min(Chainage[i - 1], Chainage[i])};
auto const to{std::max(Chainage[i - 1], Chainage[i])};
if (std::any_of(jumps.begin(), jumps.end(), [&](double const Jump) { return Jump >= from - 1e-6 && Jump <= to + 1e-6; }))
continue;
auto const k0{Km[i - 1]};
auto const k1{Km[i]};
if (std::abs(k1 - k0) < 1e-9)
continue;
auto way{plan_of(Points[i] - Points[i - 1])};
way = glm::length(way) > 1e-9 ? glm::normalize(way) : glm::dvec2{0.0, 1.0};
auto const first{static_cast<int>(std::floor(std::min(k0, k1) * 10.0 + 1e-9)) + 1};
auto const last{static_cast<int>(std::floor(std::max(k0, k1) * 10.0 + 1e-9))};
for (auto hectometres = first; hectometres <= last && hectometres < first + 10; ++hectometres)
Ticks.push_back({glm::mix(Points[i - 1], Points[i], (hectometres / 10.0 - k0) / (k1 - k0)), {-way.y, 0.0, way.x}, hectometres, false});
auto const way{kilometrage::direction_on(*axis, anchor.chainage)};
entry.ticks.push_back({kilometrage::point_on(*axis, anchor.chainage), {-way.y, 0.0, way.x}, 0, true});
}
};
std::vector<double> km;
for (auto const chainage : axis->chainage)
km.push_back(kilometrage::km_on(*axis, chainage));
hectometres_along(axis->points, km, axis->chainage, entry.ticks);
for (auto const &hectometre : kilometrage::hectometres(*axis))
entry.ticks.push_back({hectometre.at, {-hectometre.rising.y, 0.0, hectometre.rising.x}, hectometre.number, false});
m_drawn.push_back(std::move(entry));
// the shorter track where the tracks run apart, with its irregular hectometre
// the shorter track where the tracks run apart
for (auto const &side : axis->sides)
{
drawn apart;
apart.line = line;
apart.points = side.points;
apart.guessed.assign(side.points.size(), 0);
km.clear();
for (auto const along : side.along)
km.push_back(kilometrage::km_on(*axis, side, along));
hectometres_along(side.points, km, side.chainage, apart.ticks);
m_drawn.push_back(std::move(apart));
}
}
@@ -303,18 +274,8 @@ void km_tool::pick(TTrack &Track, glm::dvec3 const &Point)
else
{
// the line the point lies on, and its km there
auto best{kReadoutReach};
for (auto const &line : kilometrage::lines())
if (auto const *axis{kilometrage::axis_of(line)}; axis != nullptr)
{
double gap{0.0};
kilometrage::chainage_at(*axis, entry.point, gap);
if (gap < best)
{
best = gap;
m_line = line;
}
}
if (auto const line{kilometrage::line_at(entry.point, kReadoutReach)}; false == line.empty())
m_line = line;
double km{0.0};
if (false == m_line.empty() && kilometrage::km_at(m_line, entry.point, km, kReadoutReach))
{

View File

@@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/.
#include "editor/editorFormat.hpp"
#include "editor/editorGeometry.hpp"
#include "editor/editorIncludeInfo.hpp"
#include "editor/editorKilometrage.hpp"
#include "scene/scene.h"
#include "scene/scenelayers.h"
@@ -442,61 +443,97 @@ void hekto_tool::hekto_update()
continue;
tool.found.push_back(post);
}
if (false == tool.km_read)
tool.line_set = kilometrage::line_at(tool.point);
tool.line = tool.own_km ? std::string{} : tool.line_set;
if (tool.line.empty() && false == tool.km_read)
{
tool.km_read = true;
hekto_read_kilometrage();
}
auto const chainage_of = [&](int const Hectometres) { return tool.origin + tool.growth * (Hectometres / 10.0 - tool.km) * 1000.0; };
for (auto &post : tool.found)
{
post.expected = chainage_of(post.hectometres);
post.error = post.chainage - post.expected;
post.outside = true;
}
auto const km_at = [&](double const Chainage) { return tool.km + tool.growth * (Chainage - tool.origin) / 1000.0; };
auto const low{std::max(0, static_cast<int>(std::ceil(std::min(km_at(0.0), km_at(length)) * 10.0 - 1e-6)))};
auto const high{static_cast<int>(std::floor(std::max(km_at(0.0), km_at(length)) * 10.0 + 1e-6))};
for (int hectometres = low; hectometres <= high && tool.posts.size() < 5000; ++hectometres)
{
hekto_post post;
post.hectometres = hectometres;
post.chainage = chainage_of(hectometres);
if (post.chainage < 0.0 || post.chainage > length)
continue;
auto const axis{editor_track::sampled_position(tool.samples, post.chainage)};
auto const growing{sampled_direction(tool.samples, post.chainage) * static_cast<double>(tool.growth)};
auto const place = [&](hekto_post &Post) {
auto const axis{editor_track::sampled_position(tool.samples, Post.chainage)};
auto const growing{sampled_direction(tool.samples, Post.chainage) * static_cast<double>(Post.growth)};
glm::dvec2 const left{-growing.y, growing.x};
// alternately: the even hectometres on the right, the odd ones on the left of the growing kilometrage
auto const side{tool.side != 0 ? tool.side : (hectometres % 2 == 0 ? 1 : -1)};
auto const side{tool.side != 0 ? tool.side : (Post.hectometres % 2 == 0 ? 1 : -1)};
auto const planar{plan_of(axis) + left * (side * tool.offset)};
post.position = m_ground.placement_on_ground({planar.x, axis.y, planar.y});
post.yaw = std::fmod(heading_of(growing) + tool.turn, 360.0);
// of the posts with this number the one nearest to the place stands for it, the others are duplicates. it stands wrong
// when it's farther from the place than allowed: along the line, across it, or on the other side
auto best{std::numeric_limits<double>::max()};
for (int i = 0; i < static_cast<int>(tool.found.size()); ++i)
Post.position = m_ground.placement_on_ground({planar.x, axis.y, planar.y});
Post.yaw = std::fmod(heading_of(growing) + tool.turn, 360.0);
tool.posts.push_back(Post);
};
auto const *axis{tool.line.empty() ? nullptr : kilometrage::axis_of(tool.line)};
if (axis != nullptr)
{
// the hectometres of the kilometrage of the line, the jumps in it too
double km{0.0};
if (kilometrage::km_at(tool.line, tool.point, km))
tool.km = std::round(km * 1000.0) / 1000.0;
tool.growth = kilometrage::growth_at(tool.line, tool.point, sampled_direction(tool.samples, tool.origin)) < 0 ? -1 : 1;
for (auto const &hectometre : kilometrage::hectometres(*axis))
{
auto &found{tool.found[i]};
if (found.hectometres != hectometres)
hekto_post post;
post.hectometres = hectometre.number;
post.chainage = project(tool.samples, hectometre.at, distance);
if (distance > kLineCorridor || post.chainage <= 0.0 || post.chainage >= length || hectometre.number < 0 || tool.posts.size() >= 5000)
continue;
found.outside = false;
found.distance = plan_distance(found.post.location, post.position);
if (found.distance < best)
post.growth = glm::dot(hectometre.rising, sampled_direction(tool.samples, post.chainage)) < 0.0 ? -1 : 1;
place(post);
}
std::sort(tool.posts.begin(), tool.posts.end(), [](hekto_post const &A, hekto_post const &B) { return A.chainage < B.chainage; });
}
else
{
auto const km_at = [&](double const Chainage) { return tool.km + tool.growth * (Chainage - tool.origin) / 1000.0; };
auto const low{std::max(0, static_cast<int>(std::ceil(std::min(km_at(0.0), km_at(length)) * 10.0 - 1e-6)))};
auto const high{static_cast<int>(std::floor(std::max(km_at(0.0), km_at(length)) * 10.0 + 1e-6))};
for (int hectometres = low; hectometres <= high && tool.posts.size() < 5000; ++hectometres)
{
hekto_post post;
post.hectometres = hectometres;
post.chainage = tool.origin + tool.growth * (hectometres / 10.0 - tool.km) * 1000.0;
post.growth = tool.growth;
if (post.chainage < 0.0 || post.chainage > length)
continue;
place(post);
}
}
// each post standing goes with the place of its number nearest to it; of the posts going with a place the one nearest to it
// stands for it, the others are duplicates. it stands wrong when it's farther from the place than allowed: along the line,
// across it, or on the other side
for (auto &found : tool.found)
{
found.outside = true;
found.place = -1;
for (int i = 0; i < static_cast<int>(tool.posts.size()); ++i)
{
auto const &post{tool.posts[i]};
if (post.hectometres != found.hectometres)
continue;
auto const distance{plan_distance(found.post.location, post.position)};
if (found.place < 0 || distance < found.distance)
{
best = found.distance;
post.standing = i;
found.outside = false;
found.place = i;
found.distance = distance;
found.expected = post.chainage;
found.error = found.chainage - post.chainage;
}
}
for (int i = 0; i < static_cast<int>(tool.found.size()); ++i)
{
auto &found{tool.found[i]};
if (found.hectometres != hectometres)
continue;
found.duplicate = i != post.standing;
found.wrong = false == found.duplicate && found.distance > tool.tolerance;
}
tool.posts.push_back(post);
}
for (int i = 0; i < static_cast<int>(tool.found.size()); ++i)
{
auto const &found{tool.found[i]};
if (found.place < 0)
continue;
auto &post{tool.posts[found.place]};
if (post.standing < 0 || found.distance < tool.found[post.standing].distance)
post.standing = i;
}
for (int i = 0; i < static_cast<int>(tool.found.size()); ++i)
{
auto &found{tool.found[i]};
found.duplicate = found.place >= 0 && tool.posts[found.place].standing != i;
found.wrong = found.place >= 0 && false == found.duplicate && found.distance > tool.tolerance;
}
}
@@ -576,21 +613,40 @@ void hekto_tool::render_options()
if (Changed)
tool.dirty = true;
};
if (begin_fields("##hektokm", 0.45f))
if (false == tool.line.empty())
{
field_row("Kilometre at the point (km)");
changed(ImGui::InputDouble("##hektokilometre", &tool.km, 0.0, 0.0, "%.3f"));
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(1);
if (ImGui::Button(STR_C("Read from the posts"), ImVec2(-FLT_MIN, 0.0f)))
ImGui::TextWrapped("%s", format(STR_C("The km of line %s, as the Kilometrage tool sets them, its jumps too: km %s at the point"), tool.line.c_str(), kilometrage::text(tool.km).c_str()).c_str());
if (ImGui::SmallButton(STR_C("Give the km here instead")))
{
tool.own_km = true;
tool.km_read = false;
tool.dirty = true;
}
ImGui::EndTable();
item_tooltip("The posts go by the km given at the point and the way it grows, without the jumps of the line");
}
else
{
if (false == tool.line_set.empty() && ImGui::SmallButton(format(STR_C("Follow the km of line %s"), tool.line_set.c_str()).c_str()))
{
tool.own_km = false;
tool.dirty = true;
}
if (begin_fields("##hektokm", 0.45f))
{
field_row("Kilometre at the point (km)");
changed(ImGui::InputDouble("##hektokilometre", &tool.km, 0.0, 0.0, "%.3f"));
ImGui::TableNextRow();
ImGui::TableSetColumnIndex(1);
if (ImGui::Button(STR_C("Read from the posts"), ImVec2(-FLT_MIN, 0.0f)))
{
tool.km_read = false;
tool.dirty = true;
}
ImGui::EndTable();
}
ImGui::TextDisabled("%s", STR_C("The kilometrage grows"));
changed(segmented("growth", tool.growth, {{1, STR_C("along the line")}, {-1, STR_C("against it")}}));
}
ImGui::TextDisabled("%s", STR_C("The kilometrage grows"));
changed(segmented("growth", tool.growth, {{1, STR_C("along the line")}, {-1, STR_C("against it")}}));
ImGui::TextDisabled("%s", STR_C("Side, looking where it grows"));
changed(segmented("side", tool.side, {{1, STR_C("on the right")}, {-1, STR_C("on the left")}, {0, STR_C("alternately"), "The even hectometres on the right, the odd ones on the left"}}));
if (ImGui::TreeNode(STR_C("More settings")))
@@ -1056,6 +1112,7 @@ editor_selftest_result hekto_tool::selftest(std::string const &Command, std::ist
auto &tool{m_hekto};
if (values.count("reach"))
tool.reach = std::stod(values["reach"]);
tool.own_km = values["own"] == "1";
tool.dirty = true;
hekto_update();
if (values.count("km"))
@@ -1067,7 +1124,10 @@ editor_selftest_result hekto_tool::selftest(std::string const &Command, std::ist
tool.dirty = true;
hekto_update();
auto const missing{std::count_if(tool.posts.begin(), tool.posts.end(), [](hekto_post const &Post) { return Post.standing < 0; })};
WriteLog(format("SELFTEST hekto: km %.3f growth %d, %zu posts along %.0f m, %d to place, %zu standing. %s", tool.km, tool.growth, tool.posts.size(), tool.samples.empty() ? 0.0 : tool.samples.back().chainage, static_cast<int>(missing), tool.found.size(), tool.status.c_str()));
auto const wrong{std::count_if(tool.found.begin(), tool.found.end(), [](hekto_found const &Post) { return Post.wrong; })};
auto const duplicates{std::count_if(tool.found.begin(), tool.found.end(), [](hekto_found const &Post) { return Post.duplicate; })};
WriteLog(format("SELFTEST hekto: line %s km %.3f growth %d, %zu posts along %.0f m, %d to place, %zu standing, %d wrong, %d duplicate. %s", tool.line.empty() ? "-" : tool.line.c_str(), tool.km, tool.growth, tool.posts.size(), tool.samples.empty() ? 0.0 : tool.samples.back().chainage, static_cast<int>(missing), tool.found.size(),
static_cast<int>(wrong), static_cast<int>(duplicates), tool.status.c_str()));
for (std::size_t i = 0; i < tool.posts.size() && i < 4; ++i)
{
auto const &post{tool.posts[i]};
@@ -1076,10 +1136,17 @@ editor_selftest_result hekto_tool::selftest(std::string const &Command, std::ist
auto const direction{editor_track::sampled_position(tool.samples, post.chainage + 1.0) - axis};
glm::dvec2 const offset{post.position.x - axis.x, post.position.z - axis.z};
lateral = glm::length(offset);
auto const right{(direction.x * offset.y - direction.z * offset.x) * tool.growth >= 0.0};
auto const right{(direction.x * offset.y - direction.z * offset.x) * post.growth >= 0.0};
WriteLog(format("SELFTEST hekto post %d.%d at %.1f m, %.2f m from the axis, %s of the growing kilometrage, at %.1f %.1f %.1f", post.hectometres / 10, post.hectometres % 10, post.chainage, lateral, right ? "right" : "left", post.position.x, post.position.y,
post.position.z));
}
if (values["list"] == "1")
{
std::string list;
for (auto const &post : tool.posts)
list += format(" %d@%.0f", post.hectometres, post.chainage);
WriteLog("SELFTEST hekto posts:" + list);
}
if (values["place"] == "1")
{
hekto_place();

View File

@@ -41,6 +41,7 @@ class hekto_tool final : public editor_tool
double chainage{0.0};
glm::dvec3 position{0.0};
double yaw{0.0};
int growth{1}; // 1: the kilometrage grows along the line there, -1: against
int standing{-1}; // post already there with this number
};
struct hekto_found
@@ -54,6 +55,7 @@ class hekto_tool final : public editor_tool
bool wrong{false};
bool duplicate{false}; // another post with the same number stands nearer to its place
bool outside{false}; // its number belongs past the ends of the line
int place{-1}; // of the posts, the one with its number nearest to it
};
struct hekto_state
{
@@ -62,6 +64,9 @@ class hekto_tool final : public editor_tool
double km{0.0};
bool km_read{false}; // the kilometrage was read from the posts already standing
int growth{1}; // 1: grows along the line, -1: against
std::string line; // of the kilometrage set for the line in the Kilometrage tool the posts follow, empty for the one given here
std::string line_set; // the line the Kilometrage tool sets the kilometrage of at the point, empty for none
bool own_km{false}; // the kilometrage given here, even where the line has one set
int side{1}; // 1: to the right of the growing kilometrage, -1: to the left, 0: alternately
double offset{3.0}; // m from the axis
int style{0}; // nowy, stary, zgnity, mixed

View File

@@ -927,6 +927,86 @@ bool km_at(std::string const &Line, glm::dvec3 const &Point, double &Km, double
return true;
}
std::string line_at(glm::dvec3 const &Point, double const Reach)
{
std::string result;
auto nearest{Reach};
for (auto const &line : lines())
if (auto const *found{axis_of(line)}; found != nullptr)
{
double gap{0.0};
chainage_at(*found, Point, gap);
if (gap < nearest)
{
nearest = gap;
result = line;
}
}
return result;
}
int growth_at(std::string const &Line, glm::dvec3 const &Point, glm::dvec2 const &Way, double const Reach)
{
auto const *found{axis_of(Line)};
if (found == nullptr)
return 0;
double gap{0.0};
auto const chainage{chainage_at(*found, Point, gap)};
if (gap > Reach)
return 0;
return glm::dot(direction_on(*found, chainage) * static_cast<double>(found->growth), Way) >= 0.0 ? 1 : -1;
}
std::vector<hectometre> hectometres(axis const &Axis)
{
std::vector<hectometre> result;
std::vector<double> jumps;
for (auto const &anchor : Axis.anchors)
if (std::abs(anchor.after - anchor.before) >= 1e-6)
jumps.push_back(anchor.chainage);
auto const along = [&](std::vector<glm::dvec3> const &Points, std::vector<double> const &Km, std::vector<double> const &Chainage) {
for (std::size_t i = 1; i < Points.size(); ++i)
{
auto const from{std::min(Chainage[i - 1], Chainage[i])};
auto const to{std::max(Chainage[i - 1], Chainage[i])};
if (std::any_of(jumps.begin(), jumps.end(), [&](double const Jump) { return Jump >= from - 1e-6 && Jump <= to + 1e-6; }))
continue;
auto const k0{Km[i - 1]};
auto const k1{Km[i]};
if (std::abs(k1 - k0) < 1e-9)
continue;
auto way{plan_of(Points[i] - Points[i - 1])};
way = glm::length(way) > 1e-9 ? glm::normalize(way) : glm::dvec2{0.0, 1.0};
auto const first{static_cast<int>(std::floor(std::min(k0, k1) * 10.0 + 1e-9)) + 1};
auto const last{static_cast<int>(std::floor(std::max(k0, k1) * 10.0 + 1e-9))};
for (auto number = first; number <= last && number < first + 10; ++number)
result.push_back({glm::mix(Points[i - 1], Points[i], (number / 10.0 - k0) / (k1 - k0)), k1 > k0 ? way : -way, number});
}
};
// at a jump the post of the km on from it, as the posts of a line built anew begin there, else of the km the jump is reached at
for (auto const &anchor : Axis.anchors)
{
if (std::abs(anchor.after - anchor.before) < 1e-6)
continue;
auto const round = [](double const Km) { return std::abs(Km * 10.0 - std::round(Km * 10.0)) < 1e-4; };
auto const km{round(anchor.after) ? anchor.after : anchor.before};
if (round(km))
result.push_back({point_on(Axis, anchor.chainage), direction_on(Axis, anchor.chainage) * static_cast<double>(Axis.growth), static_cast<int>(std::lround(km * 10.0))});
}
std::vector<double> km;
for (auto const chainage : Axis.chainage)
km.push_back(km_on(Axis, chainage));
along(Axis.points, km, Axis.chainage);
for (auto const &side : Axis.sides)
{
km.clear();
for (auto const distance : side.along)
km.push_back(km_on(Axis, side, distance));
along(side.points, km, side.chainage);
}
return result;
}
TTrack *track_near(glm::dvec3 const &Point, double const Reach)
{
TTrack *best{nullptr};

View File

@@ -127,6 +127,20 @@ glm::dvec2 direction_on(axis const &Axis, double const Chainage);
double km_on(axis const &Axis, axis::side const &Side, double const Along);
// the km of the line at the point, when it lies within the reach of the axis
bool km_at(std::string const &Line, glm::dvec3 const &Point, double &Km, double const Reach = 30.0);
// the line whose axis, or a track running apart from it, passes nearest to the point within the reach, empty for none
std::string line_at(glm::dvec3 const &Point, double const Reach = 30.0);
// the km of the line rise along the way at the point: 1, against it: -1, 0 where the axis doesn't reach
int growth_at(std::string const &Line, glm::dvec3 const &Point, glm::dvec2 const &Way, double const Reach = 30.0);
// a hectometre the km of a line pass
struct hectometre
{
glm::dvec3 at{0.0};
glm::dvec2 rising{0.0, 1.0}; // the way the km rise there, in the plan
int number{0}; // of the hectometres from km 0, 1043 for km 104,3
};
// the hectometres along the axis and along the tracks running apart from it; at a jump the one of the km on from it, else of
// the km it is reached at, whichever is a whole hectometre
std::vector<hectometre> hectometres(axis const &Axis);
// the track nearest to the point in the plan within the reach, nullptr for none
TTrack *track_near(glm::dvec3 const &Point, double const Reach);
// the path of the track nearest to the point in the plan, On: the point of it nearest

View File

@@ -10780,3 +10780,15 @@ msgstr "Przez stację linia biegnie tym torem, a nie po prostych torach rozjazd
msgid "%s: this way"
msgstr "%s: tędy"
msgid "The km of line %s, as the Kilometrage tool sets them, its jumps too: km %s at the point"
msgstr "Kilometraż linii %s z narzędzia Kilometraż, razem z uskokami: km %s w punkcie"
msgid "Give the km here instead"
msgstr "Podaj kilometr tutaj"
msgid "The posts go by the km given at the point and the way it grows, without the jumps of the line"
msgstr "Słupki pójdą według kilometra podanego w punkcie i kierunku, w którym rośnie, bez uskoków linii"
msgid "Follow the km of line %s"
msgstr "Według kilometrażu linii %s"