17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-29 04:37:30 +01:00

feat(editor): straights include switches, drag to edit

This commit is contained in:
maj00r
2026-10-02 22:52:25 +02:00
parent 78c4b9f6df
commit 9198840d0f
5 changed files with 528 additions and 44 deletions

View File

@@ -2252,6 +2252,12 @@ void editor_mode::render_gizmo()
return;
}
if (straights_active())
{
render_straight_gizmo();
return;
}
if (route_active())
{
render_route_gizmo();
@@ -2738,6 +2744,7 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
{
m_track_point = {};
m_route.vertex = -1;
m_straights.handle = -1;
}
break;
@@ -2831,7 +2838,7 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
if (mode == nodebank_panel::TRACK)
{
if (false == ImGuizmo::IsOver() && false == pick_route_vertex() && false == pick_track_handle())
if (false == ImGuizmo::IsOver() && false == pick_route_vertex() && false == pick_straight_handle() && false == pick_track_handle())
{
GfxRenderer->Pick_Node_Callback([this](scene::basic_node *node) {
if (viewport_click())

View File

@@ -331,10 +331,28 @@ class editor_mode : public application_mode
editor_track::straight a;
editor_track::straight b;
char filter[32]{};
int handle{-1};
bool dragging{false};
editor_track::straight drag_line;
std::vector<std::pair<TTrack *, editor_track::state>> drag_states;
glm::dvec3 preview_start{0.0};
glm::dvec3 preview_end{0.0};
glm::mat4 gizmo{1.0f};
glm::dvec3 edit_start{0.0};
glm::dvec3 edit_end{0.0};
double edit_length{0.0};
double edit_azimuth{0.0};
double distance{4.0};
std::string status;
};
void render_straights_ui();
void draw_straights_overlay() const;
editor_track::straight const &current_straight();
bool straights_active();
bool pick_straight_handle();
void render_straight_gizmo();
void straight_apply(editor_track::straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End);
void straight_refresh();
straights_state m_straights;
enum class track_tab { straights, route, path };
track_tab m_track_tab{track_tab::straights};

View File

@@ -1124,6 +1124,7 @@ editor_track::straight const &editor_mode::current_straight()
if (track != m_straights.current_for)
{
m_straights.current_for = track;
m_straights.handle = -1;
m_straights.current = track != nullptr ? editor_track::find_straight(*track, m_straights.tolerance) : editor_track::straight{};
}
return m_straights.current;
@@ -1158,6 +1159,27 @@ void editor_mode::draw_straights_overlay() const
draw(line, i == m_straights.listed ? IM_COL32(40, 220, 255, 255) : IM_COL32(255, 255, 255, 110), i == m_straights.listed ? 4.0f : 2.0f);
}
draw(m_straights.current, IM_COL32(40, 220, 255, 255), 4.0f);
if (m_straights.dragging)
{
projection.line(drawlist, m_straights.preview_start, m_straights.preview_end, IM_COL32(255, 210, 60, 255), 3.0f);
}
else if (false == m_straights.current.tracks.empty())
{
auto const &line{m_straights.current};
glm::dvec3 const handles[] = {line.start, line.end, (line.start + line.end) * 0.5};
for (int i = 0; i < 3; ++i)
{
ImVec2 screen;
if (false == projection.project(handles[i], screen))
continue;
if (i < 2)
drawlist->AddRectFilled(ImVec2(screen.x - 6.0f, screen.y - 6.0f), ImVec2(screen.x + 6.0f, screen.y + 6.0f), IM_COL32(40, 220, 255, 255));
else
drawlist->AddCircleFilled(screen, 6.0f, IM_COL32(40, 220, 255, 255), 4);
if (i == m_straights.handle)
drawlist->AddCircle(screen, 12.0f, IM_COL32(255, 255, 255, 255), 16, 2.5f);
}
}
draw(m_straights.a, IM_COL32(60, 230, 90, 255), 5.0f);
draw(m_straights.b, IM_COL32(255, 60, 255, 255), 5.0f);
if (false == m_straights.a.tracks.empty() && false == m_straights.b.tracks.empty())
@@ -1240,7 +1262,41 @@ void editor_mode::render_straights_ui()
ImGui::SameLine();
if (ImGui::Button("Set as B"))
state.b = line;
if (false == ImGui::IsAnyItemActive())
{
state.edit_start = line.start;
state.edit_end = line.end;
state.edit_length = line.length;
state.edit_azimuth = line.azimuth;
}
ImGui::TextDisabled("Drag the ends or the middle of the straight with the gizmo, or enter the values");
ImGui::PushItemWidth(260.0f);
ImGui::InputScalarN("Start (x, y, z)", ImGuiDataType_Double, glm::value_ptr(state.edit_start), 3, nullptr, nullptr, "%.3f");
ImGui::InputScalarN("End (x, y, z)", ImGuiDataType_Double, glm::value_ptr(state.edit_end), 3, nullptr, nullptr, "%.3f");
ImGui::PopItemWidth();
if (ImGui::Button("Apply ends"))
straight_apply(line, state.edit_start, state.edit_end);
ImGui::PushItemWidth(120.0f);
ImGui::InputDouble("Length (m)", &state.edit_length, 0.0, 0.0, "%.3f");
ImGui::SameLine();
if (ImGui::Button("Set length from the start"))
{
glm::dvec3 const direction{line.direction.x, line.grade, line.direction.y};
straight_apply(line, line.start, line.start + direction * std::max(0.1, state.edit_length));
}
ImGui::InputDouble("Azimuth (deg)", &state.edit_azimuth, 0.0, 0.0, "%.5f");
ImGui::SameLine();
if (ImGui::Button("Turn around the start"))
{
auto const azimuth{glm::radians(state.edit_azimuth)};
glm::dvec3 const direction{std::sin(azimuth), line.grade, std::cos(azimuth)};
straight_apply(line, line.start, line.start + direction * line.length);
}
ImGui::PopItemWidth();
}
if (false == state.status.empty())
ImGui::TextColored(ImVec4(1.0f, 0.35f, 0.3f, 1.0f), "%s", state.status.c_str());
ImGui::Separator();
auto const &a{state.a};
@@ -1270,4 +1326,164 @@ void editor_mode::render_straights_ui()
ImGui::Text("Height of B above A: start %.3f m, end %.3f m", height(b.start), height(b.end));
ImGui::Text("Grade difference: %.2f per mille", (b.grade * (dot < 0.0 ? -1.0 : 1.0) - a.grade) * 1000.0);
ImGui::Text("Overlap along A: %.2f m", std::max(0.0, to - from));
ImGui::PushItemWidth(120.0f);
ImGui::InputDouble("Distance (m)", &state.distance, 0.0, 0.0, "%.3f");
ImGui::PopItemWidth();
ImGui::SameLine();
if (ImGui::Button("Make B parallel to A at this distance"))
{
auto const side{(startoffset + endoffset) >= 0.0 ? 1.0 : -1.0};
glm::dvec2 const direction{a.direction * (dot < 0.0 ? -1.0 : 1.0)};
glm::dvec2 const normal{-a.direction.y, a.direction.x};
auto const startalong{along(b.start)};
auto const start2d{glm::dvec2{a.start.x, a.start.z} + a.direction * startalong + normal * (side * std::abs(state.distance))};
auto const end2d{start2d + direction * b.length};
straight_apply(b, {start2d.x, b.start.y, start2d.y}, {end2d.x, b.end.y, end2d.y});
}
}
bool editor_mode::straights_active()
{
return ui()->mode() == nodebank_panel::TRACK && m_track_tab == track_tab::straights && false == current_straight().tracks.empty();
}
bool editor_mode::pick_straight_handle()
{
if (false == straights_active())
return false;
auto const &line{current_straight()};
glm::dvec3 const handles[] = {line.start, line.end, (line.start + line.end) * 0.5};
screen_projection const projection;
ImVec2 const mouse = ImGui::GetIO().MousePos;
float best = kHandleRadius * kHandleRadius;
int hit{-1};
for (int i = 0; i < 3; ++i)
{
ImVec2 screen;
if (false == projection.project(handles[i], screen))
continue;
float const dx = screen.x - mouse.x;
float const dy = screen.y - mouse.y;
if (dx * dx + dy * dy < best)
{
best = dx * dx + dy * dy;
hit = i;
}
}
if (hit < 0)
return false;
m_straights.handle = hit;
return true;
}
void editor_mode::straight_refresh()
{
auto &state{m_straights};
state.current_for = nullptr;
for (auto *line : {&state.a, &state.b})
if (false == line->tracks.empty())
*line = editor_track::find_straight(*line->tracks.front(), state.tolerance);
for (auto &line : state.found)
if (false == line.tracks.empty())
line = editor_track::find_straight(*line.tracks.front(), state.tolerance);
}
void editor_mode::straight_apply(editor_track::straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End)
{
auto &state{m_straights};
state.status.clear();
auto const tracks{editor_track::straight_affected(Line)};
for (auto *track : tracks)
{
if (false == track->Dynamics.empty())
{
state.status = "There are vehicles placed on path " + track->name();
return;
}
}
std::vector<std::pair<TTrack *, editor_track::state>> states;
for (auto *track : tracks)
states.emplace_back(track, editor_track::capture(*track));
push_track_snapshot(std::move(states));
editor_track::move_straight(Line, Start, End);
editor_track::commit(tracks);
straight_refresh();
}
void editor_mode::render_straight_gizmo()
{
auto &state{m_straights};
auto const &line{state.dragging ? state.drag_line : current_straight()};
if (state.handle < 0 || line.tracks.empty() || false == m_gizmo_enabled)
{
state.dragging = false;
return;
}
ImGuizmo::BeginFrame();
ImGuizmo::SetOrthographic(Global.EditorOrtho);
ImGuiIO const &io = ImGui::GetIO();
ImGuizmo::SetRect(0.0f, 0.0f, io.DisplaySize.x, io.DisplaySize.y);
glm::mat4 const view = GfxRenderer->Camera_View_Matrix();
glm::dvec3 const camerapos = GfxRenderer->Camera_Position();
float const aspect = io.DisplaySize.y > 0.0f ? io.DisplaySize.x / io.DisplaySize.y : 1.0f;
glm::mat4 const projection = editor_mode::projection_matrix(aspect);
glm::dvec3 const anchors[] = {line.start, line.end, (line.start + line.end) * 0.5};
auto const anchor{anchors[state.handle]};
if (false == state.dragging)
state.gizmo = glm::translate(glm::mat4(1.0f), glm::vec3(anchor - camerapos));
glm::vec3 snapvalue(m_gizmo_snap);
float const *snap = Global.ctrlState && snapvalue.x > 0.0f ? glm::value_ptr(snapvalue) : nullptr;
ImGuizmo::Manipulate(glm::value_ptr(view), glm::value_ptr(projection), ImGuizmo::TRANSLATE, ImGuizmo::WORLD, glm::value_ptr(state.gizmo), nullptr, snap);
if (ImGuizmo::IsUsing())
{
if (false == state.dragging)
{
auto const tracks{editor_track::straight_affected(line)};
if (std::any_of(tracks.begin(), tracks.end(), [](TTrack const *Track) { return false == Track->Dynamics.empty(); }))
{
state.status = "There are vehicles placed on the straight or the paths attached to it";
return;
}
state.dragging = true;
state.drag_line = line;
state.drag_states.clear();
for (auto *track : tracks)
state.drag_states.emplace_back(track, editor_track::capture(*track));
push_track_snapshot(state.drag_states);
m_track_drag = tracks;
}
auto const &original{state.drag_line};
glm::dvec3 const moved{camerapos + glm::dvec3(state.gizmo[3])};
auto start{original.start};
auto end{original.end};
if (state.handle == 0)
start = moved;
else if (state.handle == 1)
end = moved;
else
{
auto const offset{moved - (original.start + original.end) * 0.5};
start += offset;
end += offset;
}
state.preview_start = start;
state.preview_end = end;
for (auto const &entry : state.drag_states)
editor_track::apply(*entry.first, entry.second);
editor_track::move_straight(original, start, end);
m_track_dirty = true;
commit_track_drag(false);
}
else if (state.dragging)
{
commit_track_drag(true);
state.dragging = false;
m_track_drag.clear();
state.drag_states.clear();
straight_refresh();
}
}

View File

@@ -773,76 +773,161 @@ bool editor_track::find_chain(TTrack *From, TTrack *To, chain &Chain, std::strin
return true;
}
bool editor_track::is_straight(TTrack const &Track, straight_tolerance const &Tolerance)
namespace
{
if (Track.eType != tt_Normal || false == is_supported(Track) || Track.m_paths.empty())
return false;
auto const &path{Track.m_paths.front()};
glm::dvec2 const start{path.points[segment_data::point::start].x, path.points[segment_data::point::start].z};
glm::dvec2 const end{path.points[segment_data::point::end].x, path.points[segment_data::point::end].z};
auto const length{glm::distance(start, end)};
glm::dvec2 plan_of(glm::dvec3 const &Point)
{
return {Point.x, Point.z};
}
glm::dvec2 turned(glm::dvec2 const &Vector, double const Angle)
{
auto const c{std::cos(Angle)};
auto const s{std::sin(Angle)};
return {Vector.x * c - Vector.y * s, Vector.x * s + Vector.y * c};
}
double lateral_of(glm::dvec2 const &Direction, glm::dvec2 const &Offset)
{
return Direction.x * Offset.y - Direction.y * Offset.x;
}
bool plan_straight(segment_data const &Path, double const Tolerance)
{
auto const chord{plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start])};
auto const length{glm::length(chord)};
if (length < 1e-3)
return false;
auto const direction{(end - start) / length};
auto const direction{chord / length};
for (auto const index : {segment_data::point::control1, segment_data::point::control2})
{
auto const &control{path.points[index]};
glm::dvec2 const offset{control.x, control.z};
if (std::abs(direction.x * offset.y - direction.y * offset.x) > Tolerance.offset)
if (std::abs(lateral_of(direction, plan_of(Path.points[index]))) > Tolerance)
return false;
}
return true;
}
TTrack *neighbour_at(TTrack const &Track, int const Path, bool const Atend)
{
if (has_switch_paths(Track))
return Atend ? Track.SwitchExtension->pNexts[Path & 1] : Track.SwitchExtension->pPrevs[Path & 1];
return Atend ? Track.trNext : Track.trPrev;
}
void rigid_move(TTrack &Track, glm::dvec3 const &Pivot, glm::dvec3 const &Target, double const Angle)
{
for (auto &path : Track.m_paths)
{
for (auto const index : {segment_data::point::start, segment_data::point::end})
{
auto &point{path.points[index]};
auto const moved{plan_of(Target) + turned(plan_of(point - Pivot), Angle)};
point = {moved.x, point.y + Target.y - Pivot.y, moved.y};
}
for (auto const index : {segment_data::point::control1, segment_data::point::control2})
{
auto &control{path.points[index]};
auto const moved{turned(plan_of(control), Angle)};
control = {moved.x, control.y, moved.y};
}
}
}
void follow_joint(TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, double const Angle)
{
for (auto &path : Track.m_paths)
{
for (auto const atend : {false, true})
{
auto &point{path.points[atend ? segment_data::point::end : segment_data::point::start]};
if (glm::distance(point, From) > kSamePoint)
continue;
point = To;
auto &control{path.points[atend ? segment_data::point::control2 : segment_data::point::control1]};
auto const moved{turned(plan_of(control), Angle)};
control = {moved.x, control.y, moved.y};
}
}
}
} // namespace
bool editor_track::is_straight(TTrack const &Track, straight_tolerance const &Tolerance)
{
if (false == is_supported(Track) || Track.m_editorremoved)
return false;
return std::any_of(Track.m_paths.begin(), Track.m_paths.end(), [&](segment_data const &Path) { return plan_straight(Path, Tolerance.offset); });
}
editor_track::straight editor_track::find_straight(TTrack &Track, straight_tolerance const &Tolerance)
{
straight result;
if (false == is_straight(Track, Tolerance))
return result;
auto const plan = [](glm::dvec3 const &Point) { return glm::dvec2{Point.x, Point.z}; };
auto const &first{Track.m_paths.front()};
int firstpath{0};
while (false == plan_straight(Track.m_paths[firstpath], Tolerance.offset))
++firstpath;
auto const &first{Track.m_paths[firstpath]};
auto const origin{first.points[segment_data::point::start]};
auto const direction{glm::normalize(plan(first.points[segment_data::point::end] - origin))};
auto const lateral = [&](glm::dvec3 const &Point) {
auto const offset{plan(Point - origin)};
return std::abs(direction.x * offset.y - direction.y * offset.x);
};
auto const continues = [&](TTrack const *Other) {
if (Other == nullptr || false == is_straight(*Other, Tolerance))
return false;
auto const &path{Other->m_paths.front()};
auto const otherdirection{glm::normalize(plan(path.points[segment_data::point::end] - path.points[segment_data::point::start]))};
if (std::abs(direction.x * otherdirection.y - direction.y * otherdirection.x) > Tolerance.angle)
return false;
return lateral(path.points[segment_data::point::start]) <= Tolerance.offset && lateral(path.points[segment_data::point::end]) <= Tolerance.offset;
auto const direction{glm::normalize(plan_of(first.points[segment_data::point::end] - origin))};
auto const lateral = [&](glm::dvec3 const &Point) { return std::abs(lateral_of(direction, plan_of(Point - origin))); };
auto const accepts = [&](TTrack const *Other, glm::dvec3 const &Joint) {
if (Other == nullptr || false == is_supported(*Other) || Other->m_editorremoved)
return -1;
for (int i = 0; i < static_cast<int>(Other->m_paths.size()); ++i)
{
auto const &path{Other->m_paths[i]};
if (false == plan_straight(path, Tolerance.offset))
continue;
auto const &start{path.points[segment_data::point::start]};
auto const &end{path.points[segment_data::point::end]};
if (glm::distance(start, Joint) > kSamePoint && glm::distance(end, Joint) > kSamePoint)
continue;
auto const otherdirection{glm::normalize(plan_of(end - start))};
if (std::abs(lateral_of(direction, otherdirection)) > Tolerance.angle)
continue;
if (lateral(start) > Tolerance.offset || lateral(end) > Tolerance.offset)
continue;
return i;
}
return -1;
};
std::deque<TTrack *> run{&Track};
for (auto const throughend : {true, false})
std::deque<std::pair<TTrack *, int>> run{{&Track, firstpath}};
for (auto const forward : {true, false})
{
TTrack *previous{&Track};
TTrack *current{throughend ? Track.trNext : Track.trPrev};
while (continues(current) && std::find(run.begin(), run.end(), current) == run.end())
TTrack *current{&Track};
int path{firstpath};
bool atend{forward};
while (true)
{
if (throughend)
run.push_back(current);
auto const &exit{current->m_paths[path].points[atend ? segment_data::point::end : segment_data::point::start]};
auto *next{neighbour_at(*current, path, atend)};
if (std::any_of(run.begin(), run.end(), [&](auto const &Member) { return Member.first == next; }))
break;
auto const nextpath{accepts(next, exit)};
if (nextpath < 0)
break;
if (forward)
run.emplace_back(next, nextpath);
else
run.push_front(current);
auto *next{current->trPrev == previous ? current->trNext : current->trPrev};
previous = current;
run.emplace_front(next, nextpath);
atend = glm::distance(next->m_paths[nextpath].points[segment_data::point::start], exit) <= kSamePoint;
current = next;
path = nextpath;
}
}
double low{0.0}, high{0.0};
glm::dvec3 lowpoint{origin}, highpoint{origin};
for (auto *track : run)
for (auto const &member : run)
{
result.tracks.push_back(member.first);
result.paths.push_back(member.second);
for (auto const index : {segment_data::point::start, segment_data::point::end})
{
auto const &point{track->m_paths.front().points[index]};
auto const along{glm::dot(plan(point - origin), direction)};
auto const &point{member.first->m_paths[member.second].points[index]};
auto const along{glm::dot(plan_of(point - origin), direction)};
if (along < low)
{
low = along;
@@ -855,7 +940,6 @@ editor_track::straight editor_track::find_straight(TTrack &Track, straight_toler
}
}
}
result.tracks.assign(run.begin(), run.end());
result.start = lowpoint;
result.end = highpoint;
result.direction = direction;
@@ -874,7 +958,7 @@ std::vector<editor_track::straight> editor_track::find_straights(double const Mi
std::unordered_set<TTrack const *> visited;
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved || visited.count(track) > 0 || false == is_straight(*track, Tolerance))
if (track == nullptr || visited.count(track) > 0 || false == is_straight(*track, Tolerance))
continue;
auto line{find_straight(*track, Tolerance)};
for (auto const *member : line.tracks)
@@ -886,6 +970,162 @@ std::vector<editor_track::straight> editor_track::find_straights(double const Mi
return result;
}
std::vector<TTrack *> editor_track::straight_affected(straight const &Line)
{
std::vector<TTrack *> result(Line.tracks.begin(), Line.tracks.end());
auto const member = [&](TTrack const *Track) { return std::find(Line.tracks.begin(), Line.tracks.end(), Track) != Line.tracks.end(); };
for (auto *track : Line.tracks)
{
for (int i = 0; i < static_cast<int>(track->m_paths.size()); ++i)
{
for (auto const atend : {false, true})
{
auto *neighbour{neighbour_at(*track, i, atend)};
if (neighbour == nullptr || member(neighbour))
continue;
add_unique(result, neighbour);
if (false == has_switch_paths(*neighbour))
continue;
for (auto *next : neighbours(*neighbour))
if (false == member(next))
add_unique(result, next);
}
}
}
return result;
}
void editor_track::move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End)
{
if (Line.tracks.empty() || Line.length < 1e-6)
return;
auto const &direction{Line.direction};
auto const newplan{plan_of(End - Start)};
auto const newlength{glm::length(newplan)};
if (newlength < 1e-3)
return;
auto const newdirection{newplan / newlength};
auto const angle{std::atan2(lateral_of(direction, newdirection), glm::dot(direction, newdirection))};
auto const along = [&](glm::dvec3 const &Point) { return glm::dot(plan_of(Point - Line.start), direction); };
struct interval
{
double from, to;
bool rigid;
};
std::vector<interval> intervals;
double rigidlength{0.0};
for (std::size_t i = 0; i < Line.tracks.size(); ++i)
{
auto const &path{Line.tracks[i]->m_paths[Line.paths[i]]};
auto const a{along(path.points[segment_data::point::start])};
auto const b{along(path.points[segment_data::point::end])};
auto const rigid{has_switch_paths(*Line.tracks[i])};
intervals.push_back({std::min(a, b), std::max(a, b), rigid});
if (rigid)
rigidlength += std::abs(b - a);
}
std::sort(intervals.begin(), intervals.end(), [](interval const &A, interval const &B) { return A.from < B.from; });
auto const scale{Line.length - rigidlength > 1e-6 ? (newlength - rigidlength) / (Line.length - rigidlength) : 1.0};
auto const remap = [&](double const Along) {
double mapped{0.0}, previous{0.0};
for (auto const &span : intervals)
{
if (Along <= span.from)
return mapped + (Along - previous) * scale;
mapped += (span.from - previous) * scale;
previous = span.from;
auto const factor{span.rigid ? 1.0 : scale};
if (Along <= span.to)
return mapped + (Along - previous) * factor;
mapped += (span.to - previous) * factor;
previous = span.to;
}
return mapped + (Along - previous) * scale;
};
auto const transform = [&](glm::dvec3 const &Point) {
auto const a{along(Point)};
auto const offset{lateral_of(direction, plan_of(Point - Line.start))};
auto const mapped{remap(a)};
auto const oldheight{Line.start.y + (Line.end.y - Line.start.y) * a / Line.length};
auto const newheight{Start.y + (End.y - Start.y) * mapped / newlength};
auto const moved{plan_of(Start) + newdirection * mapped + glm::dvec2{-newdirection.y, newdirection.x} * offset};
return glm::dvec3{moved.x, Point.y - oldheight + newheight, moved.y};
};
auto const member = [&](TTrack const *Track) { return std::find(Line.tracks.begin(), Line.tracks.end(), Track) != Line.tracks.end(); };
std::vector<std::pair<glm::dvec3, glm::dvec3>> joints;
for (std::size_t i = 0; i < Line.tracks.size(); ++i)
{
auto &track{*Line.tracks[i]};
auto const before{track.m_paths};
if (has_switch_paths(track))
{
auto const pivot{before[Line.paths[i]].points[segment_data::point::start]};
rigid_move(track, pivot, transform(pivot), angle);
}
else
{
for (auto &path : track.m_paths)
{
for (auto const index : {segment_data::point::start, segment_data::point::end})
path.points[index] = transform(path.points[index]);
for (auto const index : {segment_data::point::control1, segment_data::point::control2})
{
auto &control{path.points[index]};
auto const moved{turned(plan_of(control), angle) * scale};
control = {moved.x, control.y * scale, moved.y};
}
}
}
for (std::size_t j = 0; j < before.size(); ++j)
for (auto const index : {segment_data::point::start, segment_data::point::end})
joints.emplace_back(before[j].points[index], track.m_paths[j].points[index]);
}
std::vector<TTrack *> moved;
for (auto *track : Line.tracks)
{
for (int i = 0; i < static_cast<int>(track->m_paths.size()); ++i)
{
for (auto const atend : {false, true})
{
auto *neighbour{neighbour_at(*track, i, atend)};
if (neighbour == nullptr || member(neighbour) || std::find(moved.begin(), moved.end(), neighbour) != moved.end())
continue;
moved.push_back(neighbour);
for (auto const &joint : joints)
{
bool touches{false};
for (auto const &path : neighbour->m_paths)
for (auto const index : {segment_data::point::start, segment_data::point::end})
touches |= glm::distance(path.points[index], joint.first) <= kSamePoint;
if (false == touches)
continue;
if (has_switch_paths(*neighbour))
{
auto const before{neighbour->m_paths};
rigid_move(*neighbour, joint.first, joint.second, angle);
for (auto *next : neighbours(*neighbour))
{
if (member(next) || next == neighbour)
continue;
for (std::size_t j = 0; j < before.size(); ++j)
for (auto const index : {segment_data::point::start, segment_data::point::end})
follow_joint(*next, before[j].points[index], neighbour->m_paths[j].points[index], angle);
}
}
else
{
follow_joint(*neighbour, joint.first, joint.second, angle);
}
break;
}
}
}
}
}
std::vector<TTrack *> editor_track::relay(chain const &Chain, std::vector<segment_data> const &Pieces)
{
std::vector<TTrack *> created;

View File

@@ -99,6 +99,7 @@ class editor_track
struct straight
{
std::vector<TTrack *> tracks;
std::vector<int> paths;
glm::dvec3 start{0.0};
glm::dvec3 end{0.0};
glm::dvec2 direction{0.0, 1.0};
@@ -114,6 +115,8 @@ class editor_track
static bool is_straight(TTrack const &Track, straight_tolerance const &Tolerance);
static straight find_straight(TTrack &Track, straight_tolerance const &Tolerance);
static std::vector<straight> find_straights(double const Minimumlength, straight_tolerance const &Tolerance);
static std::vector<TTrack *> straight_affected(straight const &Line);
static void move_straight(straight const &Line, glm::dvec3 const &Start, glm::dvec3 const &End);
static std::vector<TTrack *> relay(chain const &Chain, std::vector<segment_data> const &Pieces);
static void retire(TTrack &Track);
static void revive(TTrack &Track);