mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-11-04 22:25:45 +01:00
feat(editor): curves take over the adjoining straights
This commit is contained in:
@@ -313,6 +313,7 @@ class editor_mode : public application_mode
|
|||||||
};
|
};
|
||||||
void render_route_ui();
|
void render_route_ui();
|
||||||
void route_reset();
|
void route_reset();
|
||||||
|
void route_bind_ends();
|
||||||
bool route_from_curve(TTrack &Track);
|
bool route_from_curve(TTrack &Track);
|
||||||
void route_update() { m_route.result = alignment::compute(m_route.design); }
|
void route_update() { m_route.result = alignment::compute(m_route.design); }
|
||||||
void route_apply();
|
void route_apply();
|
||||||
|
|||||||
@@ -433,7 +433,17 @@ void editor_mode::restore_track_snapshot(EditorSnapshot const &Snapshot, std::ve
|
|||||||
if (false == m_route.chain.tracks.empty())
|
if (false == m_route.chain.tracks.empty())
|
||||||
{
|
{
|
||||||
std::string error;
|
std::string error;
|
||||||
editor_track::find_chain(m_route.from, m_route.to, m_route.chain, error);
|
if (m_route.from->m_editorremoved || m_route.to->m_editorremoved || false == editor_track::find_chain(m_route.from, m_route.to, m_route.chain, error))
|
||||||
|
{
|
||||||
|
m_route.chain = {};
|
||||||
|
m_route.result = {};
|
||||||
|
m_route.vertex = m_route.grip = -1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
route_bind_ends();
|
||||||
|
route_update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
m_track_snap = {};
|
m_track_snap = {};
|
||||||
@@ -818,6 +828,24 @@ void editor_mode::route_recommend(alignment::vertex &Vertex) const
|
|||||||
Vertex.vertical_radius = 0.0;
|
Vertex.vertical_radius = 0.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void editor_mode::route_bind_ends()
|
||||||
|
{
|
||||||
|
auto const &chain{m_route.chain};
|
||||||
|
auto &design{m_route.design};
|
||||||
|
auto const plan = [](glm::dvec3 const &Direction) { return glm::normalize(glm::dvec2{Direction.x, Direction.z}); };
|
||||||
|
auto const slope = [](glm::dvec3 const &Direction) { return Direction.y / std::max(1e-9, std::hypot(Direction.x, Direction.z)); };
|
||||||
|
design.start = chain.start;
|
||||||
|
design.end = chain.end;
|
||||||
|
design.start_direction = plan(chain.start_direction);
|
||||||
|
design.end_direction = plan(chain.end_direction);
|
||||||
|
design.start_grade = slope(chain.start_direction);
|
||||||
|
design.end_grade = slope(chain.end_direction);
|
||||||
|
design.start_radius = chain.start_radius;
|
||||||
|
design.end_radius = chain.end_radius;
|
||||||
|
design.start_reserve = editor_track::run_reserve(editor_track::straight_beyond(chain, false, m_straights.tolerance));
|
||||||
|
design.end_reserve = editor_track::run_reserve(editor_track::straight_beyond(chain, true, m_straights.tolerance));
|
||||||
|
}
|
||||||
|
|
||||||
void editor_mode::route_reset()
|
void editor_mode::route_reset()
|
||||||
{
|
{
|
||||||
auto &route{m_route};
|
auto &route{m_route};
|
||||||
@@ -837,18 +865,10 @@ void editor_mode::route_reset()
|
|||||||
design.norms = route.design.norms;
|
design.norms = route.design.norms;
|
||||||
design.shape = route.design.shape;
|
design.shape = route.design.shape;
|
||||||
design.transition_pieces = route.design.transition_pieces;
|
design.transition_pieces = route.design.transition_pieces;
|
||||||
design.start = chain.start;
|
|
||||||
design.end = chain.end;
|
|
||||||
auto const plan = [](glm::dvec3 const &Direction) { return glm::normalize(glm::dvec2{Direction.x, Direction.z}); };
|
|
||||||
auto const slope = [](glm::dvec3 const &Direction) { return Direction.y / std::max(1e-9, std::hypot(Direction.x, Direction.z)); };
|
|
||||||
design.start_direction = plan(chain.start_direction);
|
|
||||||
design.end_direction = plan(chain.end_direction);
|
|
||||||
design.start_grade = slope(chain.start_direction);
|
|
||||||
design.end_grade = slope(chain.end_direction);
|
|
||||||
design.start_radius = chain.start_radius;
|
|
||||||
design.end_radius = chain.end_radius;
|
|
||||||
design.speed = chain.velocity > 0.0 ? chain.velocity : 100.0;
|
design.speed = chain.velocity > 0.0 ? chain.velocity : 100.0;
|
||||||
route.design = design;
|
route.design = design;
|
||||||
|
route_bind_ends();
|
||||||
|
design = route.design;
|
||||||
|
|
||||||
glm::dvec2 intersection;
|
glm::dvec2 intersection;
|
||||||
if (false == alignment::collinear(design))
|
if (false == alignment::collinear(design))
|
||||||
@@ -938,16 +958,64 @@ void editor_mode::route_apply()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto const pieces{alignment::pieces(route.result, route.design, chain.tracks.size())};
|
|
||||||
std::vector<std::pair<TTrack *, editor_track::state>> states;
|
std::vector<std::pair<TTrack *, editor_track::state>> states;
|
||||||
|
std::vector<TTrack *> created;
|
||||||
|
auto *from{route.from};
|
||||||
|
auto *to{route.to};
|
||||||
|
auto const startextension{route.result.start_extension};
|
||||||
|
auto const endextension{route.result.end_extension};
|
||||||
|
if (startextension > 0.0 || endextension > 0.0)
|
||||||
|
{
|
||||||
|
auto const before{chain};
|
||||||
|
if (startextension > 0.0)
|
||||||
|
from = editor_track::take_straight(editor_track::straight_beyond(before, false, m_straights.tolerance), from, before.start, route.result.start, states, created);
|
||||||
|
if (endextension > 0.0)
|
||||||
|
to = editor_track::take_straight(editor_track::straight_beyond(before, true, m_straights.tolerance), to, before.end, route.result.end, states, created);
|
||||||
|
auto const plandistance = [](glm::dvec3 const &A, glm::dvec3 const &B) { return glm::distance(glm::dvec2{A.x, A.z}, glm::dvec2{B.x, B.z}); };
|
||||||
|
std::string error;
|
||||||
|
if (false == editor_track::find_chain(from, to, chain, error) || plandistance(chain.start, route.result.start) > 5.0 || plandistance(chain.end, route.result.end) > 5.0)
|
||||||
|
{
|
||||||
|
for (auto *track : created)
|
||||||
|
editor_track::retire(*track);
|
||||||
|
for (auto entry = states.rbegin(); entry != states.rend(); ++entry)
|
||||||
|
{
|
||||||
|
editor_track::apply(*entry->first, entry->second);
|
||||||
|
editor_track::commit({entry->first});
|
||||||
|
}
|
||||||
|
route.error = error.empty() ? "The adjoining straights can't be cut at the ends of the curve" : error;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
for (auto *track : chain.tracks)
|
for (auto *track : chain.tracks)
|
||||||
states.emplace_back(track, editor_track::capture(*track));
|
if (std::none_of(states.begin(), states.end(), [&](auto const &Entry) { return Entry.first == track; }))
|
||||||
auto created{editor_track::relay(chain, pieces)};
|
states.emplace_back(track, editor_track::capture(*track));
|
||||||
auto const added{created.size()};
|
auto pieces{alignment::pieces(route.result, route.design, chain.tracks.size())};
|
||||||
|
auto relaid{editor_track::relay(chain, pieces)};
|
||||||
|
auto const added{relaid.size()};
|
||||||
|
created.insert(created.end(), relaid.begin(), relaid.end());
|
||||||
push_track_snapshot(std::move(states), std::move(created));
|
push_track_snapshot(std::move(states), std::move(created));
|
||||||
|
|
||||||
|
route.from = from;
|
||||||
|
route.to = to;
|
||||||
editor_track::find_chain(route.from, route.to, route.chain, route.error);
|
editor_track::find_chain(route.from, route.to, route.chain, route.error);
|
||||||
|
if (startextension > 0.0 || endextension > 0.0)
|
||||||
|
{
|
||||||
|
route_bind_ends();
|
||||||
|
auto &vertices{route.design.vertices};
|
||||||
|
if (vertices.size() >= 2)
|
||||||
|
{
|
||||||
|
vertices.front().offset += startextension;
|
||||||
|
vertices.back().offset += endextension;
|
||||||
|
}
|
||||||
|
route_update();
|
||||||
|
}
|
||||||
route.status = "Re-laid " + std::to_string(chain.tracks.size()) + " paths" + (added > 0 ? ", added " + std::to_string(added) : std::string{});
|
route.status = "Re-laid " + std::to_string(chain.tracks.size()) + " paths" + (added > 0 ? ", added " + std::to_string(added) : std::string{});
|
||||||
|
if (startextension > 0.0 || endextension > 0.0)
|
||||||
|
{
|
||||||
|
char text[96];
|
||||||
|
std::snprintf(text, sizeof(text), ", the curve took over %.2f / %.2f m of the adjoining straights", startextension, endextension);
|
||||||
|
route.status += text;
|
||||||
|
}
|
||||||
WriteLog("Editor: route design - " + route.status, logtype::generic);
|
WriteLog("Editor: route design - " + route.status, logtype::generic);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1033,11 +1101,13 @@ void editor_mode::draw_route_overlay() const
|
|||||||
auto const &result{route.result};
|
auto const &result{route.result};
|
||||||
screen_projection const projection;
|
screen_projection const projection;
|
||||||
ImDrawList *drawlist = ImGui::GetBackgroundDrawList();
|
ImDrawList *drawlist = ImGui::GetBackgroundDrawList();
|
||||||
|
auto const &start{result.valid ? result.start : design.start};
|
||||||
|
auto const &end{result.valid ? result.end : design.end};
|
||||||
|
|
||||||
std::vector<glm::dvec3> polygon{design.start};
|
std::vector<glm::dvec3> polygon{start};
|
||||||
for (int i = 0; i < static_cast<int>(design.vertices.size()); ++i)
|
for (int i = 0; i < static_cast<int>(design.vertices.size()); ++i)
|
||||||
polygon.push_back(route_vertex_position(i));
|
polygon.push_back(route_vertex_position(i));
|
||||||
polygon.push_back(design.end);
|
polygon.push_back(end);
|
||||||
for (std::size_t i = 0; i + 1 < polygon.size(); ++i)
|
for (std::size_t i = 0; i + 1 < polygon.size(); ++i)
|
||||||
projection.line(drawlist, polygon[i], polygon[i + 1], IM_COL32(255, 210, 60, 150), 1.5f);
|
projection.line(drawlist, polygon[i], polygon[i + 1], IM_COL32(255, 210, 60, 150), 1.5f);
|
||||||
|
|
||||||
@@ -1062,7 +1132,7 @@ void editor_mode::draw_route_overlay() const
|
|||||||
|
|
||||||
for (auto const atend : {false, true})
|
for (auto const atend : {false, true})
|
||||||
{
|
{
|
||||||
auto const &point{atend ? design.end : design.start};
|
auto const &point{atend ? end : start};
|
||||||
auto const direction{atend ? design.end_direction : design.start_direction};
|
auto const direction{atend ? design.end_direction : design.start_direction};
|
||||||
ImVec2 screen;
|
ImVec2 screen;
|
||||||
if (projection.project(point, screen))
|
if (projection.project(point, screen))
|
||||||
@@ -1369,7 +1439,10 @@ void editor_mode::render_route_ui()
|
|||||||
auto const pieces{alignment::minimum_pieces(result, design)};
|
auto const pieces{alignment::minimum_pieces(result, design)};
|
||||||
ImGui::Text("Length %.2f m (now %.2f m), %zu elements", result.length, route.chain.length, result.elements.size());
|
ImGui::Text("Length %.2f m (now %.2f m), %zu elements", result.length, route.chain.length, result.elements.size());
|
||||||
ImGui::TextDisabled("Paths: %zu in the fragment, %zu needed%s", route.chain.tracks.size(), pieces, pieces > route.chain.tracks.size() ? " (copies of the neighbours will be added)" : "");
|
ImGui::TextDisabled("Paths: %zu in the fragment, %zu needed%s", route.chain.tracks.size(), pieces, pieces > route.chain.tracks.size() ? " (copies of the neighbours will be added)" : "");
|
||||||
|
if (result.start_extension > 0.0 || result.end_extension > 0.0)
|
||||||
|
ImGui::Text("Takes over %.2f m of the straight at the start, %.2f m at the end", result.start_extension, result.end_extension);
|
||||||
}
|
}
|
||||||
|
ImGui::TextDisabled("Straight available beyond the ends: %.2f / %.2f m", design.start_reserve, design.end_reserve);
|
||||||
for (auto const &error : result.errors)
|
for (auto const &error : result.errors)
|
||||||
ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.3f, 1.0f), "%s", error.c_str());
|
ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.3f, 1.0f), "%s", error.c_str());
|
||||||
for (auto const &warning : result.warnings)
|
for (auto const &warning : result.warnings)
|
||||||
@@ -2165,6 +2238,7 @@ void editor_mode::straight_reshape(editor_track::straight const &Line, double co
|
|||||||
state.status = m_route.error;
|
state.status = m_route.error;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
m_route.design.start_reserve = m_route.design.end_reserve = 0.0;
|
||||||
for (auto &vertex : m_route.design.vertices)
|
for (auto &vertex : m_route.design.vertices)
|
||||||
{
|
{
|
||||||
vertex.radius = Radius;
|
vertex.radius = Radius;
|
||||||
@@ -2185,7 +2259,7 @@ void editor_mode::straight_reshape(editor_track::straight const &Line, double co
|
|||||||
state.status = m_route.result.errors.empty() ? "The curve can't be fitted" : m_route.result.errors.front();
|
state.status = m_route.result.errors.empty() ? "The curve can't be fitted" : m_route.result.errors.front();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
auto const pieces{alignment::pieces(m_route.result, m_route.design, m_route.chain.tracks.size())};
|
auto pieces{alignment::pieces(m_route.result, m_route.design, m_route.chain.tracks.size())};
|
||||||
auto created{editor_track::relay(m_route.chain, pieces)};
|
auto created{editor_track::relay(m_route.chain, pieces)};
|
||||||
editor_track::commit(tracks);
|
editor_track::commit(tracks);
|
||||||
for (auto const &entry : splitstates)
|
for (auto const &entry : splitstates)
|
||||||
|
|||||||
@@ -277,11 +277,18 @@ recommendation recommend(double const Speed, double const Radius, limits const &
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
result compute(design const &Design)
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
result fit_between(design const &Design)
|
||||||
{
|
{
|
||||||
result r;
|
result r;
|
||||||
r.shape = Design.shape;
|
r.shape = Design.shape;
|
||||||
r.gauge = Design.norms.gauge;
|
r.gauge = Design.norms.gauge;
|
||||||
|
r.start = Design.start;
|
||||||
|
r.end = Design.end;
|
||||||
|
auto const startreserve{std::max(0.0, Design.start_reserve)};
|
||||||
|
auto const endreserve{std::max(0.0, Design.end_reserve)};
|
||||||
|
|
||||||
auto const start{plan(Design.start)};
|
auto const start{plan(Design.start)};
|
||||||
auto const end{plan(Design.end)};
|
auto const end{plan(Design.end)};
|
||||||
@@ -351,6 +358,16 @@ result compute(design const &Design)
|
|||||||
straight.length = Length;
|
straight.length = Length;
|
||||||
push(straight);
|
push(straight);
|
||||||
};
|
};
|
||||||
|
auto const starts_early = [&](double const Straight) {
|
||||||
|
if (Straight < 0.0)
|
||||||
|
r.start_extension = -Straight;
|
||||||
|
if (Straight >= -1e-4 || -Straight <= startreserve + 1e-4)
|
||||||
|
return;
|
||||||
|
if (startreserve > 0.0)
|
||||||
|
r.errors.emplace_back(format("Vertex 1: the curve starts %.2f m before the adjoining straight", -Straight - startreserve));
|
||||||
|
else
|
||||||
|
r.errors.emplace_back(format("Vertex 1: the curve starts %.2f m before the fixed start", -Straight));
|
||||||
|
};
|
||||||
|
|
||||||
struct fitted
|
struct fitted
|
||||||
{
|
{
|
||||||
@@ -441,9 +458,9 @@ result compute(design const &Design)
|
|||||||
std::vector<double> scales(count, 1.0);
|
std::vector<double> scales(count, 1.0);
|
||||||
for (std::size_t leg = 0; leg + 1 < polygon.size(); ++leg)
|
for (std::size_t leg = 0; leg + 1 < polygon.size(); ++leg)
|
||||||
{
|
{
|
||||||
auto const available{glm::distance(polygon[leg], polygon[leg + 1])};
|
auto const available{glm::distance(polygon[leg], polygon[leg + 1]) + (leg == 0 ? startreserve : 0.0) + (leg == count ? endreserve : 0.0)};
|
||||||
auto const required{(leg > 0 ? fits[leg - 1].tangent_out : 0.0) + (leg < count ? fits[leg].tangent_in : 0.0)};
|
auto const required{(leg > 0 ? fits[leg - 1].tangent_out : 0.0) + (leg < count ? fits[leg].tangent_in : 0.0)};
|
||||||
if (required <= available)
|
if (required <= available + 1e-6)
|
||||||
continue;
|
continue;
|
||||||
auto const scale{available / required * (1.0 - 1e-9)};
|
auto const scale{available / required * (1.0 - 1e-9)};
|
||||||
if (leg > 0)
|
if (leg > 0)
|
||||||
@@ -534,13 +551,10 @@ result compute(design const &Design)
|
|||||||
auto const along{cross(directionout, endoffset) / cross(directionout, directionin)};
|
auto const along{cross(directionout, endoffset) / cross(directionout, directionin)};
|
||||||
auto const curvestart{position - directionin * along};
|
auto const curvestart{position - directionin * along};
|
||||||
auto const straight{glm::dot(curvestart - cursor, directionin)};
|
auto const straight{glm::dot(curvestart - cursor, directionin)};
|
||||||
if (straight < -1e-4)
|
if (k == 0)
|
||||||
{
|
starts_early(straight);
|
||||||
if (k == 0)
|
else if (straight < -1e-4)
|
||||||
r.errors.emplace_back(format("Vertex 1: the curve starts %.2f m before the fixed start", -straight));
|
r.errors.emplace_back(format("Curves of vertices %zu and %zu overlap by %.2f m", k, k + 1, -straight));
|
||||||
else
|
|
||||||
r.errors.emplace_back(format("Curves of vertices %zu and %zu overlap by %.2f m", k, k + 1, -straight));
|
|
||||||
}
|
|
||||||
push_straight(cursor, directionin, straight);
|
push_straight(cursor, directionin, straight);
|
||||||
auto const startchainage{chainage};
|
auto const startchainage{chainage};
|
||||||
glm::dvec2 origin{curvestart};
|
glm::dvec2 origin{curvestart};
|
||||||
@@ -615,13 +629,10 @@ result compute(design const &Design)
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto const straight{glm::dot(curvestart - cursor, directionin)};
|
auto const straight{glm::dot(curvestart - cursor, directionin)};
|
||||||
if (straight < -1e-4)
|
if (k == 0)
|
||||||
{
|
starts_early(straight);
|
||||||
if (k == 0)
|
else if (straight < -1e-4)
|
||||||
r.errors.emplace_back(format("Vertex 1: the curve starts %.2f m before the fixed start", -straight));
|
r.errors.emplace_back(format("Curves of vertices %zu and %zu overlap by %.2f m", k, k + 1, -straight));
|
||||||
else
|
|
||||||
r.errors.emplace_back(format("Curves of vertices %zu and %zu overlap by %.2f m", k, k + 1, -straight));
|
|
||||||
}
|
|
||||||
push_straight(cursor, directionin, straight);
|
push_straight(cursor, directionin, straight);
|
||||||
|
|
||||||
element curve;
|
element curve;
|
||||||
@@ -698,8 +709,15 @@ result compute(design const &Design)
|
|||||||
}
|
}
|
||||||
auto const lastdirection{glm::normalize(polygon.back() - polygon[polygon.size() - 2])};
|
auto const lastdirection{glm::normalize(polygon.back() - polygon[polygon.size() - 2])};
|
||||||
auto const tail{glm::dot(end - cursor, lastdirection)};
|
auto const tail{glm::dot(end - cursor, lastdirection)};
|
||||||
if (tail < -1e-4)
|
if (tail < 0.0)
|
||||||
r.errors.emplace_back(format("Vertex %zu: the curve ends %.2f m past the fixed end", count, -tail));
|
r.end_extension = -tail;
|
||||||
|
if (tail < -1e-4 && -tail > endreserve + 1e-4)
|
||||||
|
{
|
||||||
|
if (endreserve > 0.0)
|
||||||
|
r.errors.emplace_back(format("Vertex %zu: the curve ends %.2f m past the adjoining straight", count, -tail - endreserve));
|
||||||
|
else
|
||||||
|
r.errors.emplace_back(format("Vertex %zu: the curve ends %.2f m past the fixed end", count, -tail));
|
||||||
|
}
|
||||||
push_straight(cursor, lastdirection, tail);
|
push_straight(cursor, lastdirection, tail);
|
||||||
r.length = chainage;
|
r.length = chainage;
|
||||||
|
|
||||||
@@ -757,6 +775,39 @@ result compute(design const &Design)
|
|||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
glm::dvec3 along_end(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, double const Distance)
|
||||||
|
{
|
||||||
|
auto const direction{glm::normalize(Direction)};
|
||||||
|
return {Point.x + direction.x * Distance, Point.y + Grade * Distance, Point.z + direction.y * Distance};
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
result compute(design const &Design)
|
||||||
|
{
|
||||||
|
auto first{fit_between(Design)};
|
||||||
|
auto const startextension{first.start_extension > 1e-4 ? first.start_extension : 0.0};
|
||||||
|
auto const endextension{first.end_extension > 1e-4 ? first.end_extension : 0.0};
|
||||||
|
if (false == first.errors.empty() || (startextension == 0.0 && endextension == 0.0))
|
||||||
|
{
|
||||||
|
first.start_extension = first.end_extension = 0.0;
|
||||||
|
return first;
|
||||||
|
}
|
||||||
|
auto extended{Design};
|
||||||
|
extended.start = along_end(Design.start, Design.start_direction, Design.start_grade, -startextension);
|
||||||
|
extended.end = along_end(Design.end, Design.end_direction, Design.end_grade, endextension);
|
||||||
|
extended.start_reserve = extended.end_reserve = 0.0;
|
||||||
|
if (extended.vertices.size() >= 2)
|
||||||
|
{
|
||||||
|
extended.vertices.front().offset += startextension;
|
||||||
|
extended.vertices.back().offset += endextension;
|
||||||
|
}
|
||||||
|
auto r{fit_between(extended)};
|
||||||
|
r.start_extension = startextension;
|
||||||
|
r.end_extension = endextension;
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
bool tangent_intersection(design const &Design, glm::dvec2 &Point)
|
bool tangent_intersection(design const &Design, glm::dvec2 &Point)
|
||||||
{
|
{
|
||||||
auto const start{plan(Design.start)};
|
auto const start{plan(Design.start)};
|
||||||
|
|||||||
@@ -65,6 +65,9 @@ struct design
|
|||||||
double end_grade{0.0};
|
double end_grade{0.0};
|
||||||
double start_radius{0.0};
|
double start_radius{0.0};
|
||||||
double end_radius{0.0};
|
double end_radius{0.0};
|
||||||
|
// length of the straight beyond the start and the end, which the curves can take over
|
||||||
|
double start_reserve{0.0};
|
||||||
|
double end_reserve{0.0};
|
||||||
std::vector<vertex> vertices;
|
std::vector<vertex> vertices;
|
||||||
transition_shape shape{transition_shape::cubic_parabola};
|
transition_shape shape{transition_shape::cubic_parabola};
|
||||||
double speed{100.0};
|
double speed{100.0};
|
||||||
@@ -152,6 +155,10 @@ struct result
|
|||||||
std::vector<curve_report> curves;
|
std::vector<curve_report> curves;
|
||||||
std::vector<profile_point> profile;
|
std::vector<profile_point> profile;
|
||||||
double length{0.0};
|
double length{0.0};
|
||||||
|
glm::dvec3 start{0.0};
|
||||||
|
glm::dvec3 end{0.0};
|
||||||
|
double start_extension{0.0};
|
||||||
|
double end_extension{0.0};
|
||||||
};
|
};
|
||||||
|
|
||||||
result compute(design const &Design);
|
result compute(design const &Design);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ http:
|
|||||||
#include <numeric>
|
#include <numeric>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <tuple>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
@@ -1179,6 +1180,121 @@ bool editor_track::find_curve(TTrack &Track, straight_tolerance const &Tolerance
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
|
||||||
|
// shorter remainders of a straight path are absorbed by moving the joint instead of splitting the path
|
||||||
|
double split_margin(double const Length)
|
||||||
|
{
|
||||||
|
return std::max(0.5, 0.002 * Length);
|
||||||
|
}
|
||||||
|
|
||||||
|
double plan_length(segment_data const &Path)
|
||||||
|
{
|
||||||
|
return glm::length(plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start]));
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
editor_track::straight_run editor_track::straight_beyond(chain const &Chain, bool const Atend, straight_tolerance const &Tolerance)
|
||||||
|
{
|
||||||
|
straight_run run;
|
||||||
|
if (Chain.tracks.empty())
|
||||||
|
return run;
|
||||||
|
auto *edge{Atend ? Chain.tracks.back() : Chain.tracks.front()};
|
||||||
|
bool const forward{Atend ? Chain.forward.back() : Chain.forward.front()};
|
||||||
|
TTrack *current{forward == Atend ? edge->trNext : edge->trPrev};
|
||||||
|
auto joint{Atend ? Chain.end : Chain.start};
|
||||||
|
auto const origin{plan_of(joint)};
|
||||||
|
auto direction{glm::normalize(plan_of(Atend ? Chain.end_direction : -Chain.start_direction))};
|
||||||
|
while (current != nullptr && run.tracks.size() < 1000)
|
||||||
|
{
|
||||||
|
std::string reason;
|
||||||
|
if (current->eType != tt_Normal || current->m_editorremoved || false == can_edit_geometry(*current, reason))
|
||||||
|
break;
|
||||||
|
if (std::find(Chain.tracks.begin(), Chain.tracks.end(), current) != Chain.tracks.end() || std::find(run.tracks.begin(), run.tracks.end(), current) != run.tracks.end())
|
||||||
|
break;
|
||||||
|
auto const &path{current->m_paths.front()};
|
||||||
|
if (false == plan_straight(path, Tolerance))
|
||||||
|
break;
|
||||||
|
bool outward;
|
||||||
|
if (glm::distance(path.points[segment_data::point::start], joint) <= kSamePoint)
|
||||||
|
outward = true;
|
||||||
|
else if (glm::distance(path.points[segment_data::point::end], joint) <= kSamePoint)
|
||||||
|
outward = false;
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
auto const &far{path.points[outward ? segment_data::point::end : segment_data::point::start]};
|
||||||
|
if (run.tracks.empty())
|
||||||
|
{
|
||||||
|
auto const own{glm::normalize(plan_of(far - joint))};
|
||||||
|
if (std::abs(lateral_of(direction, own)) > 0.01 || glm::dot(direction, own) <= 0.0)
|
||||||
|
break;
|
||||||
|
direction = own;
|
||||||
|
}
|
||||||
|
auto const offset{plan_of(far) - origin};
|
||||||
|
auto const along{glm::dot(offset, direction)};
|
||||||
|
if (along <= run.length + 1e-3 || std::abs(lateral_of(direction, offset)) > Tolerance.offset)
|
||||||
|
break;
|
||||||
|
run.tracks.push_back(current);
|
||||||
|
run.outward.push_back(outward);
|
||||||
|
run.length = along;
|
||||||
|
joint = far;
|
||||||
|
current = outward ? current->trNext : current->trPrev;
|
||||||
|
}
|
||||||
|
return run;
|
||||||
|
}
|
||||||
|
|
||||||
|
double editor_track::run_reserve(straight_run const &Run)
|
||||||
|
{
|
||||||
|
if (Run.tracks.empty())
|
||||||
|
return 0.0;
|
||||||
|
return std::max(0.0, Run.length - split_margin(plan_length(Run.tracks.back()->m_paths.front())) - 0.01);
|
||||||
|
}
|
||||||
|
|
||||||
|
TTrack *editor_track::take_straight(straight_run const &Run, TTrack *Edge, glm::dvec3 const &Joint, glm::dvec3 const &Cut, std::vector<std::pair<TTrack *, state>> &States, std::vector<TTrack *> &Created)
|
||||||
|
{
|
||||||
|
auto const length{glm::length(plan_of(Cut - Joint))};
|
||||||
|
auto *edge{Edge};
|
||||||
|
double covered{0.0};
|
||||||
|
for (std::size_t i = 0; i < Run.tracks.size(); ++i)
|
||||||
|
{
|
||||||
|
auto *track{Run.tracks[i]};
|
||||||
|
auto const outward{Run.outward[i]};
|
||||||
|
auto const &path{track->m_paths.front()};
|
||||||
|
auto const pathlength{plan_length(path)};
|
||||||
|
auto const margin{split_margin(pathlength)};
|
||||||
|
auto const remaining{length - covered};
|
||||||
|
if (remaining <= margin)
|
||||||
|
{
|
||||||
|
if (std::abs(remaining) > 1e-4)
|
||||||
|
{
|
||||||
|
States.emplace_back(track, capture(*track));
|
||||||
|
move_point(*track, {0, outward ? point_kind::start : point_kind::end}, Cut);
|
||||||
|
commit({track});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (remaining < pathlength - margin)
|
||||||
|
{
|
||||||
|
auto const &nearpoint{path.points[outward ? segment_data::point::start : segment_data::point::end]};
|
||||||
|
auto const &farpoint{path.points[outward ? segment_data::point::end : segment_data::point::start]};
|
||||||
|
auto const t{nearest_parameter(*track, nearpoint + (farpoint - nearpoint) * (remaining / pathlength))};
|
||||||
|
auto const before{capture(*track)};
|
||||||
|
auto *created{split_path(*track, t)};
|
||||||
|
if (created == nullptr)
|
||||||
|
break;
|
||||||
|
States.emplace_back(track, before);
|
||||||
|
Created.push_back(created);
|
||||||
|
edge = outward ? track : created;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
edge = track;
|
||||||
|
covered += pathlength;
|
||||||
|
}
|
||||||
|
return edge;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<editor_track::switch_template> editor_track::find_switch_templates()
|
std::vector<editor_track::switch_template> editor_track::find_switch_templates()
|
||||||
{
|
{
|
||||||
std::vector<switch_template> result;
|
std::vector<switch_template> result;
|
||||||
|
|||||||
@@ -137,6 +137,16 @@ class editor_track
|
|||||||
double split{0.5};
|
double split{0.5};
|
||||||
};
|
};
|
||||||
static bool find_curve(TTrack &Track, straight_tolerance const &Tolerance, double const Gauge, curve &Curve);
|
static bool find_curve(TTrack &Track, straight_tolerance const &Tolerance, double const Gauge, curve &Curve);
|
||||||
|
// regular straight paths continuing a chain end in line, which the chain can take over
|
||||||
|
struct straight_run
|
||||||
|
{
|
||||||
|
std::vector<TTrack *> tracks;
|
||||||
|
std::vector<bool> outward;
|
||||||
|
double length{0.0};
|
||||||
|
};
|
||||||
|
static straight_run straight_beyond(chain const &Chain, bool const Atend, straight_tolerance const &Tolerance);
|
||||||
|
static double run_reserve(straight_run const &Run);
|
||||||
|
static TTrack *take_straight(straight_run const &Run, TTrack *Edge, glm::dvec3 const &Joint, glm::dvec3 const &Cut, std::vector<std::pair<TTrack *, state>> &States, std::vector<TTrack *> &Created);
|
||||||
struct switch_template
|
struct switch_template
|
||||||
{
|
{
|
||||||
TTrack *source{nullptr};
|
TTrack *source{nullptr};
|
||||||
|
|||||||
Reference in New Issue
Block a user