mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-29 10:27:32 +01:00
refactor(editor): split the track mode into its gizmo, kept records, switch drive, switch slide and track bed files; path geometry shared in editorGeometry instead of four copies of the reversed path, track shape rules (stretching, rolls at joints, middle, switch axis, drive spot) in editor_track, fitting the track to the ground in ground_follow with the terrain query passed in, the record layer and the switch classification in the stores; long gizmo, record watch and switch slide functions broken up, out-parameters replaced by optional results
This commit is contained in:
@@ -178,6 +178,8 @@ set(SOURCES
|
||||
"editor/editorTrack.cpp"
|
||||
"editor/editorRoad.cpp"
|
||||
"editor/editorAlignment.cpp"
|
||||
"editor/editorTrackShape.cpp"
|
||||
"editor/editorGroundFollow.cpp"
|
||||
"editor/editorAlignmentStore.cpp"
|
||||
"editor/editorSwitchStore.cpp"
|
||||
"editor/editorSpeed.cpp"
|
||||
@@ -190,6 +192,11 @@ set(SOURCES
|
||||
"editor/editorIcons.cpp"
|
||||
"application/editormode.cpp"
|
||||
"application/editormodetrack.cpp"
|
||||
"application/editormodetrackgizmo.cpp"
|
||||
"application/editormoderecords.cpp"
|
||||
"application/editormodeswitchdrive.cpp"
|
||||
"application/editormodeswitchslide.cpp"
|
||||
"application/editormodetrackbed.cpp"
|
||||
"application/editormodetrackux.cpp"
|
||||
"application/editormoderoad.cpp"
|
||||
"application/editormodeprofile.cpp"
|
||||
|
||||
@@ -707,8 +707,27 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
void track_gizmo_control(TTrack &Track, glm::dvec3 Control);
|
||||
void track_gizmo_point(TTrack &Track, glm::dvec3 const &Position);
|
||||
void track_gizmo_switches(TTrack &Track);
|
||||
void track_gizmo_conform(TTrack &Track) const;
|
||||
bool track_gizmo_clustered(TTrack const *Track) const;
|
||||
void track_gizmo_spill(TTrack &Track, std::size_t const Pieces);
|
||||
void track_gizmo_carry(track_gizmo_drag::switch_joint &Link, glm::dvec3 const &Point, glm::dvec3 const &From, glm::dvec3 const &To, double const Grade);
|
||||
void track_gizmo_roll_switch(track_gizmo_drag::switch_joint const &Link, double const Roll);
|
||||
void track_gizmo_take_joints(TTrack &Track);
|
||||
void track_gizmo_take_points(TTrack &Track);
|
||||
void track_gizmo_take_partners(TTrack &Track);
|
||||
void track_gizmo_take_switch_joints(TTrack &Track, bool const Controlmode);
|
||||
track_gizmo_drag::switch_joint track_gizmo_link(TTrack &Track, editor_track::point_ref const &Own, std::pair<TTrack *, editor_track::point_ref> const &Connection, std::function<bool(TTrack const *)> const &Movable);
|
||||
void track_gizmo_take_switches(TTrack &Track);
|
||||
void track_gizmo_measure_rotation(TTrack &Track, bool const Pointmode);
|
||||
void track_gizmo_measure(TTrack &Track, bool const Pointmode, gizmo_operation const Operation);
|
||||
void track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_operation const Operation);
|
||||
void track_gizmo_turn_direction(TTrack &Track, double const Angle);
|
||||
void track_gizmo_turn_grade(TTrack &Track, double const Angle);
|
||||
void track_gizmo_turn_cant(TTrack &Track, double const Angle);
|
||||
void track_gizmo_turn(TTrack &Track, int const Axis, double const Angle);
|
||||
void track_gizmo_rotated(TTrack &Track);
|
||||
void track_gizmo_scaled(TTrack &Track);
|
||||
void track_gizmo_moved_point(TTrack &Track, glm::dvec3 const &Position);
|
||||
void track_gizmo_length(TTrack &Track, double Length);
|
||||
void track_gizmo_end(TTrack &Track);
|
||||
bool m_track_dirty{false};
|
||||
@@ -807,7 +826,7 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
void route_take_stored(int const Index);
|
||||
void record_switch(TTrack &Track, editor_track::switch_template const &Shape);
|
||||
void record_slip(std::vector<TTrack *> const &Parts, std::string const &Kind, std::string const &Type, double const Radius, double const Angle, std::string const &Base);
|
||||
void render_switch_record(TTrack &Track);
|
||||
void render_switch_record(TTrack const &Track) const;
|
||||
void route_store();
|
||||
bool digitize_drawn(std::vector<TTrack *> const &Created, bool const Alone = true);
|
||||
bool digitize_chain(editor_track::chain const &Chain);
|
||||
@@ -956,6 +975,7 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
};
|
||||
switch_tool m_switch;
|
||||
bool switch_room(TTrack &Track, double &Low, double &High, std::string &Reason);
|
||||
bool switch_vacate(TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, double const Shift, editor_track::straight const &Line, editor_track::edit &Edit, std::string &Status);
|
||||
bool move_switch_paths(TTrack &Track, std::vector<segment_data> const &Paths, std::string &Status);
|
||||
bool slide_switch(TTrack &Track, double const Distance);
|
||||
bool switch_slides(TTrack const &Track) const;
|
||||
@@ -1406,10 +1426,16 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
};
|
||||
// includes of the templates whose file names the test accepts, with the location in p2..p4 and the rotation in p5
|
||||
std::vector<standing_template> standing_templates(std::function<bool(std::string const &)> const &Accept) const;
|
||||
bool find_switch_drive(TTrack const &Switch, standing_template &Drive, int &Family, int &Index);
|
||||
struct switch_drive
|
||||
{
|
||||
standing_template standing;
|
||||
int family{-1};
|
||||
int index{0};
|
||||
};
|
||||
std::optional<switch_drive> find_switch_drive(TTrack const &Switch);
|
||||
std::string missing_drive_variant(int const Family, bool const Rightswitch, bool const Driveright) const;
|
||||
bool replace_switch_drive(TTrack &Switch, int Family, bool Flip);
|
||||
void apply_switch_drive_choice(TTrack &Switch);
|
||||
bool switch_drive_spot(TTrack const &Switch, glm::dvec3 &Location, double &Yaw, glm::dvec2 &Tilt, bool &Rightswitch) const;
|
||||
void switch_drive_captured(TTrack const &Track);
|
||||
void switch_drives_committed(std::vector<TTrack *> const &Tracks);
|
||||
// places them in the active layer, as one step of the undo. returns: how many were placed
|
||||
|
||||
322
application/editormoderecords.cpp
Normal file
322
application/editormoderecords.cpp
Normal file
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "application/editormode.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editormodeshared.h"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
|
||||
#include "world/Track.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "simulation/simulation.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "utilities/translation.h"
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <cstdio>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using geometry::bezier;
|
||||
using geometry::plan_of;
|
||||
using geometry::signed_angle;
|
||||
using geometry::turned;
|
||||
using path_ends = std::pair<glm::dvec3, glm::dvec3>;
|
||||
|
||||
struct record_drift
|
||||
{
|
||||
int followed{0};
|
||||
int departed{0};
|
||||
double departure{0.0};
|
||||
double cant{0.0};
|
||||
std::string layer;
|
||||
};
|
||||
|
||||
glm::dvec2 chord_direction(glm::dvec3 const &Start, glm::dvec3 const &End)
|
||||
{
|
||||
auto const direction{plan_of(End - Start)};
|
||||
return glm::length(direction) > 1e-9 ? glm::normalize(direction) : glm::dvec2{0.0, 1.0};
|
||||
}
|
||||
|
||||
path_ends const &ends_before(std::vector<TTrack *> const &Moving, std::vector<path_ends> const &Before, TTrack const *Track)
|
||||
{
|
||||
return Before[static_cast<std::size_t>(std::find(Moving.begin(), Moving.end(), Track) - Moving.begin())];
|
||||
}
|
||||
|
||||
bool lies_on(int const Record, TTrack const &Track)
|
||||
{
|
||||
bezier const curve{Track.m_paths.front()};
|
||||
std::array<double, 3> constexpr samples{0.0, 0.5, 1.0};
|
||||
return std::ranges::all_of(samples, [&](double const T) { return alignment_store::on_record(Record, curve.point(T), alignment_store::tolerance).has_value(); });
|
||||
}
|
||||
|
||||
bool covers_record(int const Record, std::vector<TTrack *> const &Moving)
|
||||
{
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
if (track != nullptr && false == track->m_editorremoved && track->eType == tt_Normal && false == track->m_paths.empty() && std::find(Moving.begin(), Moving.end(), track) == Moving.end() && lies_on(Record, *track))
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool carried_rigidly(int const Record, std::vector<TTrack *> const &Tracks, std::vector<TTrack *> const &Moving, std::vector<path_ends> const &Before, record_drift &Drift)
|
||||
{
|
||||
auto const &first{ends_before(Moving, Before, Tracks.front())};
|
||||
auto const &path{Tracks.front()->m_paths.front()};
|
||||
auto const &start{path.points[segment_data::point::start]};
|
||||
auto const from{chord_direction(first.first, first.second)};
|
||||
auto const to{chord_direction(start, path.points[segment_data::point::end])};
|
||||
auto const angle{signed_angle(from, to)};
|
||||
auto const carried = [&](glm::dvec3 const &Old, glm::dvec3 const &Current) { return glm::distance(plan_of(start) + turned(plan_of(Old) - plan_of(first.first), angle), plan_of(Current)) <= 0.01; };
|
||||
for (auto const *track : Tracks)
|
||||
{
|
||||
auto const &was{ends_before(Moving, Before, track)};
|
||||
auto const &now{track->m_paths.front()};
|
||||
if (false == carried(was.first, now.points[segment_data::point::start]) || false == carried(was.second, now.points[segment_data::point::end]))
|
||||
return false;
|
||||
}
|
||||
if (glm::distance(first.first, start) > 1e-4 || std::abs(angle) > 1e-7)
|
||||
{
|
||||
alignment_store::transform(Record, first.first, from, start, to);
|
||||
++Drift.followed;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void measure_departure(int const Record, std::vector<TTrack *> const &Tracks, record_drift &Drift)
|
||||
{
|
||||
auto const &records{alignment_store::records()};
|
||||
auto const gauge{records[Record].design.norms.gauge};
|
||||
for (auto const *track : Tracks)
|
||||
{
|
||||
if (track->m_editorremoved)
|
||||
continue;
|
||||
auto const &path{track->m_paths.front()};
|
||||
bezier const curve{path};
|
||||
double largest{0.0};
|
||||
for (auto const t : {0.0, 0.5, 1.0})
|
||||
{
|
||||
auto const found{alignment_store::on_record(Record, curve.point(t), 1000.0)};
|
||||
largest = std::max(largest, found.has_value() ? found->offset : 1000.0);
|
||||
}
|
||||
if (largest > alignment_store::tolerance)
|
||||
{
|
||||
++Drift.departed;
|
||||
Drift.departure = std::max(Drift.departure, largest);
|
||||
Drift.layer = scene::Layers.layer(records[Record].layer).name;
|
||||
continue;
|
||||
}
|
||||
for (auto const end : {0, 1})
|
||||
if (auto const found{alignment_store::on_record(Record, curve.point(end), alignment_store::tolerance)})
|
||||
{
|
||||
auto const actual{gauge * std::sin(std::abs(glm::radians(static_cast<double>(path.rolls[end]))))};
|
||||
Drift.cant = std::max(Drift.cant, std::abs(actual - alignment_store::cant_at(Record, found->chainage)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> drift_notes(record_drift const &Drift)
|
||||
{
|
||||
std::vector<std::string> notes;
|
||||
if (Drift.followed > 0)
|
||||
notes.push_back(STR("The kept design of the line went along with its paths"));
|
||||
if (Drift.departed > 0)
|
||||
notes.push_back(format(STR_C("%d path(s) left the design kept in %s, by up to %.2f m: Ctrl+Z takes it back, the route design lays them by the design again or forgets it"), Drift.departed, Drift.layer.c_str(), Drift.departure));
|
||||
if (Drift.cant > 5.0)
|
||||
notes.push_back(format(STR_C("The cant differs from the kept design by up to %.0f mm"), Drift.cant));
|
||||
return notes;
|
||||
}
|
||||
|
||||
switch_store::record switch_entry(editor_track::switch_template const &Shape)
|
||||
{
|
||||
switch_store::record entry;
|
||||
if (Shape.source == nullptr)
|
||||
{
|
||||
entry.type = Shape.type;
|
||||
entry.rail = Shape.rail;
|
||||
entry.radius = Shape.radius;
|
||||
entry.ratio = Shape.ratio;
|
||||
entry.a = Shape.a;
|
||||
entry.b = Shape.b;
|
||||
entry.source = "designed";
|
||||
}
|
||||
else if (auto const known{switch_store::of(*Shape.source)}; known.has_value() && known->kind == "switch")
|
||||
{
|
||||
entry = *known;
|
||||
entry.number = "-";
|
||||
}
|
||||
else
|
||||
{
|
||||
entry.type = "-";
|
||||
entry.radius = Shape.radius;
|
||||
entry.ratio = Shape.ratio;
|
||||
entry.source = "measured";
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
template <std::size_t Size>
|
||||
bool edit_dashed(char const *Label, std::string &Value)
|
||||
{
|
||||
std::array<char, Size> text{};
|
||||
std::snprintf(text.data(), text.size(), "%s", Value == "-" ? "" : Value.c_str());
|
||||
if (false == ImGui::InputText(Label, text.data(), text.size(), ImGuiInputTextFlags_EnterReturnsTrue) && false == ImGui::IsItemDeactivatedAfterEdit())
|
||||
return false;
|
||||
Value = text[0] != '\0' ? text.data() : "-";
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void editor_mode::records_watch_begin()
|
||||
{
|
||||
auto &watch{m_record_watch};
|
||||
watch = {};
|
||||
watch.active = true;
|
||||
watch.moving = m_track_drag;
|
||||
for (auto const &link : m_track_gizmo_drag.switches)
|
||||
for (auto const &member : link.cluster)
|
||||
if (std::find(watch.moving.begin(), watch.moving.end(), member.first) == watch.moving.end())
|
||||
watch.moving.push_back(member.first);
|
||||
for (auto *track : watch.moving)
|
||||
{
|
||||
auto const &path{track->m_paths.front()};
|
||||
watch.before.emplace_back(path.points[segment_data::point::start], path.points[segment_data::point::end]);
|
||||
if (track->eType != tt_Normal)
|
||||
continue;
|
||||
if (auto const found{alignment_store::on_track(*track)})
|
||||
watch.on[found->record].push_back(track);
|
||||
}
|
||||
for (auto const &[record, tracks] : watch.on)
|
||||
if (covers_record(record, watch.moving))
|
||||
watch.whole.insert(record);
|
||||
}
|
||||
|
||||
// what the drag of the gizmo did to the kept alignments and switches: an alignment all of whose paths were moved or turned as a
|
||||
// whole goes along with them, paths pulled out of an alignment or given another cant are told about, as are switches bent out
|
||||
// of the shape of their type; switches moved as a whole take their records along
|
||||
void editor_mode::records_watch_end()
|
||||
{
|
||||
auto &watch{m_record_watch};
|
||||
if (false == watch.active)
|
||||
return;
|
||||
watch.active = false;
|
||||
record_drift drift;
|
||||
for (auto const &[record, tracks] : watch.on)
|
||||
if (watch.whole.count(record) == 0 || false == carried_rigidly(record, tracks, watch.moving, watch.before, drift))
|
||||
measure_departure(record, tracks, drift);
|
||||
auto notes{drift_notes(drift)};
|
||||
switch_store::follow();
|
||||
for (auto const *track : watch.moving)
|
||||
if (track->eType == tt_Switch && false == track->m_editorremoved)
|
||||
if (auto const known{switch_store::named(*track)}; known.has_value() && false == switch_store::of(*track).has_value())
|
||||
notes.push_back(format(STR_C("Switch %s lost the shape of its type %s: Ctrl+Z takes it back"), track->name().c_str(), switch_store::label(*known).c_str()));
|
||||
records_to_history();
|
||||
if (false == notes.empty())
|
||||
{
|
||||
std::string text;
|
||||
for (auto const ¬e : notes)
|
||||
text += (text.empty() ? "" : "\n") + note;
|
||||
ui()->set_status(text, drift.departed > 0 || drift.cant > 5.0 || notes.size() > static_cast<std::size_t>(drift.followed > 0 ? 1 : 0));
|
||||
WriteLog("Editor: " + text, logtype::generic);
|
||||
}
|
||||
watch = {};
|
||||
}
|
||||
|
||||
void editor_mode::records_to_history()
|
||||
{
|
||||
auto alignments{alignment_store::take_changes()};
|
||||
auto switches{switch_store::take_changes()};
|
||||
if (m_history.empty() || m_history.back().action != EditorSnapshot::Action::TrackEdit)
|
||||
return;
|
||||
auto &last{m_history.back()};
|
||||
if (alignments.has_value() && false == last.alignments.has_value())
|
||||
last.alignments = std::move(alignments);
|
||||
if (switches.has_value() && false == last.switch_records.has_value())
|
||||
last.switch_records = std::move(switches);
|
||||
}
|
||||
|
||||
// the switch is kept in the scenery with its type: of the catalogue template, of the switch of the scenery it was copied
|
||||
// from, or measured; on a curve it is an arc switch, one-sided when it diverges to the side the curve turns to
|
||||
void editor_mode::record_switch(TTrack &Track, editor_track::switch_template const &Shape)
|
||||
{
|
||||
if (Track.eType != tt_Switch || Track.m_paths.size() < 2 || Track.name().empty() || scene::Layers.empty())
|
||||
return;
|
||||
auto entry{switch_entry(Shape)};
|
||||
entry.kind = "switch";
|
||||
entry.name = Track.name();
|
||||
entry.parts = {entry.name};
|
||||
switch_store::place(entry, Track);
|
||||
switch_store::classify(entry, Track);
|
||||
switch_store::put(entry, alignment_store::layer_of(Track));
|
||||
}
|
||||
|
||||
void editor_mode::record_slip(std::vector<TTrack *> const &Parts, std::string const &Kind, std::string const &Type, double const Radius, double const Angle, std::string const &Base)
|
||||
{
|
||||
if (Parts.empty() || scene::Layers.empty())
|
||||
return;
|
||||
switch_store::record entry;
|
||||
entry.kind = Kind;
|
||||
entry.name = Base;
|
||||
entry.type = Type;
|
||||
entry.radius = Radius;
|
||||
entry.ratio = Angle > 1e-9 ? 1.0 / std::tan(Angle) : 0.0;
|
||||
for (auto const *part : Parts)
|
||||
if (part != nullptr && false == part->name().empty() && (Kind == "crossing" || part->eType == tt_Switch))
|
||||
entry.parts.push_back(part->name());
|
||||
if (entry.parts.empty())
|
||||
return;
|
||||
auto const *first{simulation::Paths.find(entry.parts.front())};
|
||||
if (first == nullptr || first->m_paths.empty())
|
||||
return;
|
||||
switch_store::place(entry, *first);
|
||||
switch_store::put(entry, alignment_store::layer_of(*first));
|
||||
}
|
||||
|
||||
void editor_mode::render_switch_record(TTrack const &Track) const
|
||||
{
|
||||
auto const known{switch_store::of(Track)};
|
||||
if (false == known.has_value())
|
||||
{
|
||||
ImGui::TextDisabled("%s", STR_C("The scenery keeps no type for this switch, it's read from its shape"));
|
||||
return;
|
||||
}
|
||||
auto entry{*known};
|
||||
ImGui::Text(STR_C("Type kept in the scenery: %s"), switch_store::label(entry).c_str());
|
||||
if (entry.kind == "switch" && entry.main_radius != 0.0)
|
||||
ImGui::TextDisabled(STR_C("Diverging track R %.0f m: the R %.0f m of the type bent with the main track"), switch_store::branch_radius(entry), entry.radius);
|
||||
if (entry.source == "measured")
|
||||
ImGui::TextDisabled("%s", STR_C("Copied from a switch of the scenery: the size is measured from its shape"));
|
||||
ImGui::PushItemWidth(100.0f);
|
||||
bool changed{edit_dashed<32>(STR_C("Number##switchnumber"), entry.number)};
|
||||
item_tooltip("Number of the switch in the station, as the plans of the station give it");
|
||||
ImGui::SameLine();
|
||||
changed |= edit_dashed<16>(STR_C("Rail##switchrail"), entry.rail);
|
||||
item_tooltip("Rail of the switch: 60E1, 49E1...");
|
||||
auto km{entry.km.has_value() ? *entry.km / 1000.0 : 0.0};
|
||||
if (ImGui::InputDouble(STR_C("Km of the start##switchkm"), &km, 0.0, 0.0, "%.3f", ImGuiInputTextFlags_EnterReturnsTrue))
|
||||
{
|
||||
entry.km = km * 1000.0;
|
||||
changed = true;
|
||||
}
|
||||
item_tooltip("Chainage of the start of the switch; taken from the horizontal alignment kept in the scenery when the switch lies on one");
|
||||
ImGui::PopItemWidth();
|
||||
if (changed)
|
||||
{
|
||||
switch_store::put(entry, entry.layer);
|
||||
switch_store::settle();
|
||||
}
|
||||
}
|
||||
21
application/editormodeshared.h
Normal file
21
application/editormodeshared.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include "utilities/translation.h"
|
||||
|
||||
float constexpr kDragPixels{5.0f};
|
||||
|
||||
inline void item_tooltip(char const *Text)
|
||||
{
|
||||
if (ImGui::IsItemHovered())
|
||||
ImGui::SetTooltip("%s", STR_C(Text));
|
||||
}
|
||||
@@ -46,6 +46,7 @@ namespace
|
||||
using geometry::bezier;
|
||||
using geometry::plan_distance;
|
||||
using geometry::plan_of;
|
||||
using geometry::reversed;
|
||||
|
||||
double const kSweepRouteReach{2000.0}; // m each way through the switches
|
||||
double const kSweepOutlineStep{1.0};
|
||||
@@ -114,17 +115,6 @@ bool platform_distance()
|
||||
return true;
|
||||
}
|
||||
|
||||
segment_data reversed(segment_data const &Path)
|
||||
{
|
||||
segment_data result{Path};
|
||||
result.points[segment_data::point::start] = Path.points[segment_data::point::end];
|
||||
result.points[segment_data::point::end] = Path.points[segment_data::point::start];
|
||||
result.points[segment_data::point::control1] = Path.points[segment_data::point::control2];
|
||||
result.points[segment_data::point::control2] = Path.points[segment_data::point::control1];
|
||||
result.rolls = {-Path.rolls[1], -Path.rolls[0]};
|
||||
return result;
|
||||
}
|
||||
|
||||
bool same_piece(segment_data const &A, segment_data const &B)
|
||||
{
|
||||
for (int i = 0; i < 4; ++i)
|
||||
|
||||
309
application/editormodeswitchdrive.cpp
Normal file
309
application/editormodeswitchdrive.cpp
Normal file
@@ -0,0 +1,309 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "application/editormode.h"
|
||||
#include "application/editormodeshared.h"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorIncludeInfo.hpp"
|
||||
|
||||
#include "world/Track.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "utilities/translation.h"
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <iterator>
|
||||
#include <map>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
std::string drive_file(std::string Text)
|
||||
{
|
||||
std::replace(Text.begin(), Text.end(), '\\', '/');
|
||||
std::transform(Text.begin(), Text.end(), Text.begin(), [](unsigned char Character) { return static_cast<char>(std::tolower(Character)); });
|
||||
return Text;
|
||||
}
|
||||
|
||||
int drive_side(std::string const &Side)
|
||||
{
|
||||
if (Side == "left")
|
||||
return 0;
|
||||
return Side == "right" ? 1 : -1;
|
||||
}
|
||||
|
||||
std::optional<std::string> pose_value(std::string const &Role, glm::dvec3 const &Local, editor_track::drive_spot const &Spot)
|
||||
{
|
||||
if (Role == "pos.x")
|
||||
return editor_includes::number(Local.x);
|
||||
if (Role == "pos.y")
|
||||
return editor_includes::number(Local.y);
|
||||
if (Role == "pos.z")
|
||||
return editor_includes::number(Local.z);
|
||||
if (Role == "rot.y")
|
||||
return editor_includes::number(Spot.yaw);
|
||||
if (Role == "rot.x")
|
||||
return editor_includes::number(Spot.tilt.x);
|
||||
if (Role == "rot.z")
|
||||
return editor_includes::number(Spot.tilt.y);
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
double angle_value(std::vector<std::string> const &Values, std::size_t const Index)
|
||||
{
|
||||
double value{0.0};
|
||||
if (Index < Values.size())
|
||||
std::sscanf(Values[Index].c_str(), "%lf", &value);
|
||||
return value;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void editor_mode::scan_switch_drives()
|
||||
{
|
||||
auto &tool{m_switch};
|
||||
if (tool.drives_scanned)
|
||||
return;
|
||||
tool.drives_scanned = true;
|
||||
tool.drives.clear();
|
||||
if (false == EditorIncludes.scanned())
|
||||
EditorIncludes.scan();
|
||||
for (auto const &entry : EditorIncludes.entries())
|
||||
{
|
||||
if (entry.category != "switch drive")
|
||||
continue;
|
||||
include_info info;
|
||||
std::string error;
|
||||
if (false == editor_includes::load(entry.file, info, error) || editor_includes::parameter_with_role(info, "track") == 0)
|
||||
continue;
|
||||
auto const hand{drive_side(info.switch_hand)};
|
||||
auto const drive{drive_side(info.switch_drive)};
|
||||
if (hand < 0 || drive < 0)
|
||||
continue;
|
||||
auto const name{info.name.empty() ? entry.file : info.name};
|
||||
auto family{std::find_if(tool.drives.begin(), tool.drives.end(), [&](switch_tool::drive_family const &Family) { return Family.name == name; })};
|
||||
if (family == tool.drives.end())
|
||||
{
|
||||
tool.drives.push_back({name, {}});
|
||||
family = std::prev(tool.drives.end());
|
||||
}
|
||||
family->files[hand * 2 + drive] = entry.file;
|
||||
family->heights[hand * 2 + drive] = info.switch_height;
|
||||
family->preferred |= info.switch_default;
|
||||
}
|
||||
std::sort(tool.drives.begin(), tool.drives.end(), [](switch_tool::drive_family const &A, switch_tool::drive_family const &B) { return A.name < B.name; });
|
||||
auto const preferred{std::find_if(tool.drives.begin(), tool.drives.end(), [](switch_tool::drive_family const &Family) { return Family.preferred; })};
|
||||
if (tool.drives.empty())
|
||||
tool.drive = -1;
|
||||
else
|
||||
tool.drive = preferred != tool.drives.end() ? static_cast<int>(preferred - tool.drives.begin()) : 0;
|
||||
}
|
||||
|
||||
void editor_mode::switch_drive_captured(TTrack const &Track)
|
||||
{
|
||||
if (Track.eType != tt_Switch || m_infra_suspended)
|
||||
return;
|
||||
auto const found{find_switch_drive(Track)};
|
||||
if (false == found.has_value())
|
||||
return;
|
||||
auto const instance{found->standing.instance};
|
||||
if (std::any_of(m_drive_buffer.begin(), m_drive_buffer.end(), [&](auto const &Entry) { return Entry.first == instance; }))
|
||||
return;
|
||||
m_drive_buffer.emplace_back(instance, scene::Layers.directive(instance));
|
||||
}
|
||||
|
||||
void editor_mode::switch_drives_committed(std::vector<TTrack *> const &Tracks)
|
||||
{
|
||||
if (m_infra_suspended)
|
||||
return;
|
||||
for (auto const *track : Tracks)
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch)
|
||||
continue;
|
||||
auto const found{find_switch_drive(*track)};
|
||||
auto const spot{editor_track::drive_spot_of(*track)};
|
||||
if (false == found.has_value() || false == spot.has_value())
|
||||
continue;
|
||||
auto const &drive{found->standing};
|
||||
auto const &included{scene::Layers.instance(drive.instance)};
|
||||
include_info info;
|
||||
std::string error;
|
||||
if (included.file == nullptr || false == editor_includes::load(*included.file, info, error))
|
||||
continue;
|
||||
auto const local{spot->location + glm::dvec3{0.0, m_switch.drives[found->family].heights[found->index], 0.0} - included.context.offset};
|
||||
auto values{drive.values};
|
||||
for (auto const ¶meter : info.parameters)
|
||||
{
|
||||
if (parameter.id < 1 || parameter.id > static_cast<int>(values.size()))
|
||||
continue;
|
||||
if (auto const value{pose_value(parameter.role, local, *spot)})
|
||||
values[parameter.id - 1] = *value;
|
||||
}
|
||||
auto const directive{editor_includes::compose_directive(*included.file, values)};
|
||||
if (directive != scene::Layers.directive(drive.instance))
|
||||
set_include_directive(drive.instance, directive);
|
||||
}
|
||||
}
|
||||
|
||||
std::string editor_mode::missing_drive_variant(int const Family, bool const Rightswitch, bool const Driveright) const
|
||||
{
|
||||
return format(STR_C("The drive %s has no variant for a %s switch with the drive on the %s"), m_switch.drives[Family].name.c_str(), Rightswitch ? STR_C("right") : STR_C("left"), Driveright ? STR_C("right") : STR_C("left"));
|
||||
}
|
||||
|
||||
bool editor_mode::place_switch_drive(TTrack &Switch)
|
||||
{
|
||||
auto &tool{m_switch};
|
||||
scan_switch_drives();
|
||||
if (tool.drive < 0 || tool.drive >= static_cast<int>(tool.drives.size()) || Switch.eType != tt_Switch || Switch.m_paths.size() < 2)
|
||||
return false;
|
||||
auto const spot{editor_track::drive_spot_of(Switch)};
|
||||
if (false == spot.has_value())
|
||||
return false;
|
||||
bool const driveright{(tool.drive_side == 0) == spot->rightswitch};
|
||||
auto const &family{tool.drives[tool.drive]};
|
||||
auto const index{(spot->rightswitch ? 2 : 0) + (driveright ? 1 : 0)};
|
||||
template_item item;
|
||||
item.file = family.files[index];
|
||||
if (item.file.empty())
|
||||
{
|
||||
tool.status = missing_drive_variant(tool.drive, spot->rightswitch, driveright);
|
||||
return false;
|
||||
}
|
||||
item.location = spot->location + glm::dvec3{0.0, family.heights[index], 0.0};
|
||||
item.yaw = spot->yaw;
|
||||
item.described = true;
|
||||
item.track = Switch.name();
|
||||
item.tilt = spot->tilt;
|
||||
if (auto const present{find_switch_drive(Switch)})
|
||||
return replace_switch_drive(Switch, tool.drive, (present->index & 1) != (driveright ? 1 : 0));
|
||||
std::string error;
|
||||
if (place_templates({item}, error) == 0)
|
||||
{
|
||||
tool.status = STR_C("The drive couldn't be placed: ") + error;
|
||||
return false;
|
||||
}
|
||||
tool.status = format(STR_C("Switch %s with the drive %s: events %s+ and %s- switch it, also in the map"), Switch.name().c_str(), item.file.c_str(), Switch.name().c_str(), Switch.name().c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::optional<editor_mode::switch_drive> editor_mode::find_switch_drive(TTrack const &Switch)
|
||||
{
|
||||
auto const &tool{m_switch};
|
||||
scan_switch_drives();
|
||||
std::map<std::string, std::pair<int, int>, std::less<>> variants;
|
||||
for (int family = 0; family < static_cast<int>(tool.drives.size()); ++family)
|
||||
for (int index = 0; index < 4; ++index)
|
||||
if (false == tool.drives[family].files[index].empty())
|
||||
variants[drive_file(tool.drives[family].files[index])] = {family, index};
|
||||
auto const name{drive_file(Switch.name())};
|
||||
auto const standing{standing_templates([&](std::string const &File) { return variants.contains(File); })};
|
||||
auto const found{std::find_if(standing.begin(), standing.end(), [&](standing_template const &Standing) { return drive_file(Standing.values[0]) == name; })};
|
||||
if (found == standing.end())
|
||||
return std::nullopt;
|
||||
auto const &[family, index]{variants[found->file]};
|
||||
return switch_drive{*found, family, index};
|
||||
}
|
||||
|
||||
void editor_mode::apply_switch_drive_choice(TTrack &Switch)
|
||||
{
|
||||
if (m_switch.drive >= 0)
|
||||
{
|
||||
place_switch_drive(Switch);
|
||||
return;
|
||||
}
|
||||
if (auto const found{find_switch_drive(Switch)}; found.has_value() && remove_templates({found->standing.instance}) > 0)
|
||||
m_switch.status = format(STR_C("Switch %s without a drive now"), Switch.name().c_str());
|
||||
}
|
||||
|
||||
bool editor_mode::flip_switch_drive(TTrack &Switch)
|
||||
{
|
||||
return replace_switch_drive(Switch, -1, true);
|
||||
}
|
||||
|
||||
bool editor_mode::replace_switch_drive(TTrack &Switch, int const Family, bool const Flip)
|
||||
{
|
||||
auto &tool{m_switch};
|
||||
auto const present{find_switch_drive(Switch)};
|
||||
if (false == present.has_value())
|
||||
{
|
||||
tool.status = format(STR_C("No drive of the switch %s among the described drives"), Switch.name().c_str());
|
||||
return false;
|
||||
}
|
||||
auto const &found{present->standing};
|
||||
auto const family{Family >= 0 ? Family : present->family};
|
||||
auto const index{Flip ? present->index ^ 1 : present->index};
|
||||
template_item item;
|
||||
item.file = tool.drives[family].files[index];
|
||||
if (item.file.empty())
|
||||
{
|
||||
tool.status = Flip ? format(STR_C("The drive %s has no variant with the drive on the other side"), tool.drives[family].name.c_str()) : missing_drive_variant(family, (index & 2) != 0, (index & 1) != 0);
|
||||
return false;
|
||||
}
|
||||
if (drive_file(item.file) == found.file)
|
||||
return false;
|
||||
auto const spot{editor_track::drive_spot_of(Switch)};
|
||||
item.location = spot.has_value() ? spot->location + glm::dvec3{0.0, tool.drives[family].heights[index], 0.0} : found.location;
|
||||
item.yaw = found.yaw;
|
||||
item.described = true;
|
||||
item.track = Switch.name();
|
||||
item.tilt = {angle_value(found.values, 5), angle_value(found.values, 6)};
|
||||
if (remove_templates({found.instance}) == 0)
|
||||
{
|
||||
tool.status = STR_C("The drive of this switch can't be changed in its file");
|
||||
return false;
|
||||
}
|
||||
std::string error;
|
||||
if (place_templates({item}, error) == 0)
|
||||
{
|
||||
scene::Layers.removed(found.instance, false);
|
||||
m_history.pop_back();
|
||||
tool.status = STR_C("The drive couldn't be placed: ") + error;
|
||||
return false;
|
||||
}
|
||||
join_last_steps();
|
||||
tool.status = format(index & 1 ? STR_C("Switch %s: the drive %s, on the right now") : STR_C("Switch %s: the drive %s, on the left now"), Switch.name().c_str(), item.file.c_str());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_mode::render_switch_drive_choice()
|
||||
{
|
||||
auto &tool{m_switch};
|
||||
scan_switch_drives();
|
||||
if (tool.drives.empty())
|
||||
{
|
||||
ImGui::TextDisabled("%s", STR_C("No switch drives: no template is described with category: switch drive"));
|
||||
return false;
|
||||
}
|
||||
auto const label = [&](int const Index) { return Index < 0 ? std::string{STR_C("no drive")} : tool.drives[Index].name; };
|
||||
ImGui::PushItemWidth(200.0f);
|
||||
bool changed{false};
|
||||
if (ImGui::BeginCombo(STR_C("Drive"), label(tool.drive).c_str()))
|
||||
{
|
||||
for (int i = -1; i < static_cast<int>(tool.drives.size()); ++i)
|
||||
if (ImGui::Selectable(label(i).c_str(), tool.drive == i))
|
||||
{
|
||||
tool.drive = i;
|
||||
changed = true;
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
item_tooltip("Templates described with category: switch drive, the side of the switch and of the drive, and a parameter with the role track for the name of the switch.\nIts events <name>+ and <name>- switch it with the keys by the switch and in the map during the game");
|
||||
if (tool.drive >= 0)
|
||||
{
|
||||
ImGui::SameLine();
|
||||
changed |= ImGui::RadioButton(STR_C("diverging side"), &tool.drive_side, 0);
|
||||
ImGui::SameLine();
|
||||
changed |= ImGui::RadioButton(STR_C("other side"), &tool.drive_side, 1);
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
348
application/editormodeswitchslide.cpp
Normal file
348
application/editormodeswitchslide.cpp
Normal file
@@ -0,0 +1,348 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "application/editormode.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
#include "application/editormodeshared.h"
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
|
||||
#include "world/Track.h"
|
||||
#include "utilities/translation.h"
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include <algorithm>
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using geometry::bezier;
|
||||
using geometry::has_controls;
|
||||
using geometry::plan_of;
|
||||
|
||||
struct switch_end_move
|
||||
{
|
||||
glm::dvec3 from{0.0};
|
||||
glm::dvec3 to{0.0};
|
||||
double shift{0.0};
|
||||
};
|
||||
|
||||
std::string track_label(TTrack const &Track)
|
||||
{
|
||||
return Track.name().empty() ? std::string{"(noname)"} : Track.name();
|
||||
}
|
||||
|
||||
std::optional<std::vector<switch_end_move>> switch_end_moves(TTrack const &Track, segment_data const &Main, bool const Online, std::pair<double, double> const &Room, double const Offset, std::string &Status)
|
||||
{
|
||||
auto const axis{editor_track::main_axis(Track)};
|
||||
std::vector<switch_end_move> ends;
|
||||
for (auto const index : {segment_data::point::start, segment_data::point::end})
|
||||
{
|
||||
auto const from{Track.m_paths.front().points[index]};
|
||||
auto const to{Main.points[index]};
|
||||
if (false == Online || glm::distance(from, to) < 0.005 || axis->lateral(to) > Offset)
|
||||
continue;
|
||||
auto const target{axis->along(to)};
|
||||
if (target < Room.first - 0.01 || target > Room.second + 0.01)
|
||||
{
|
||||
Status = STR_C("The switch would run into another switch or past the end of the straight");
|
||||
return std::nullopt;
|
||||
}
|
||||
ends.push_back({from, to, index == segment_data::point::start ? axis->along(from) - target : target - axis->along(from)});
|
||||
}
|
||||
std::stable_sort(ends.begin(), ends.end(), [](switch_end_move const &A, switch_end_move const &B) { return A.shift > B.shift; });
|
||||
return ends;
|
||||
}
|
||||
|
||||
std::string switch_rejoin(TTrack &Track, std::vector<editor_track::joint> const &Joints, std::vector<bool> const &Handled, editor_track::edit &Edit)
|
||||
{
|
||||
std::vector<editor_track::joint> following;
|
||||
std::string loose;
|
||||
for (std::size_t i = 0; i < Joints.size(); ++i)
|
||||
{
|
||||
auto const &joint{Joints[i]};
|
||||
if (Handled[i] || joint.other->m_editorremoved)
|
||||
continue;
|
||||
auto const &theirs{joint.other->m_paths[joint.theirs.path]};
|
||||
auto const shift{glm::distance(editor_track::point_position(Track, joint.own), editor_track::point_position(*joint.other, joint.theirs))};
|
||||
if (shift > 0.3 * bezier{theirs}.plan_length())
|
||||
{
|
||||
loose += (loose.empty() ? "" : ", ") + track_label(*joint.other);
|
||||
continue;
|
||||
}
|
||||
following.push_back(joint);
|
||||
Edit.capture(*joint.other);
|
||||
}
|
||||
editor_track::follow(Track, following);
|
||||
return loose;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool editor_mode::switch_room(TTrack &Track, double &Low, double &High, std::string &Reason)
|
||||
{
|
||||
Low = 0.0;
|
||||
High = 0.0;
|
||||
if (Track.eType != tt_Switch || Track.m_paths.size() < 2 || has_controls(Track.m_paths.front()))
|
||||
return false;
|
||||
auto const axis{editor_track::main_axis(Track)};
|
||||
if (false == axis.has_value())
|
||||
return false;
|
||||
auto const line{editor_track::find_straight(Track, m_straights.tolerance)};
|
||||
if (false == tracks_editable(line.tracks, Reason))
|
||||
return false;
|
||||
std::vector<std::tuple<double, double, bool>> spans;
|
||||
for (std::size_t i = 0; i < line.tracks.size(); ++i)
|
||||
{
|
||||
if (line.tracks[i] == &Track)
|
||||
continue;
|
||||
auto const &path{line.tracks[i]->m_paths[line.paths[i]]};
|
||||
auto const a{axis->along(path.points[segment_data::point::start])};
|
||||
auto const b{axis->along(path.points[segment_data::point::end])};
|
||||
spans.emplace_back(std::min(a, b), std::max(a, b), line.tracks[i]->eType == tt_Normal);
|
||||
}
|
||||
std::sort(spans.begin(), spans.end());
|
||||
double low{0.0};
|
||||
double high{axis->length};
|
||||
for (auto const &[from, to, plain] : spans)
|
||||
if (from > axis->length - 0.01)
|
||||
{
|
||||
if (false == plain || from > high + 0.01)
|
||||
break;
|
||||
high = std::max(high, to);
|
||||
}
|
||||
for (auto span{spans.rbegin()}; span != spans.rend(); ++span)
|
||||
{
|
||||
auto const &[from, to, plain] = *span;
|
||||
if (to < 0.01)
|
||||
{
|
||||
if (false == plain || to < low - 0.01)
|
||||
break;
|
||||
low = std::min(low, from);
|
||||
}
|
||||
}
|
||||
Low = low;
|
||||
High = high;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_mode::switch_vacate(TTrack &Track, glm::dvec3 const &From, glm::dvec3 const &To, double const Shift, editor_track::straight const &Line, editor_track::edit &Edit, std::string &Status)
|
||||
{
|
||||
if (Shift > 0.0)
|
||||
{
|
||||
auto const a{Line.along(From)};
|
||||
auto const b{Line.along(To)};
|
||||
if (cut_straight(Line, std::min(a, b), std::max(a, b), Edit.states, Edit.created, Edit.removed, nullptr))
|
||||
return true;
|
||||
Status = STR_C("The switch would overlap another switch, move it onto plain track");
|
||||
return false;
|
||||
}
|
||||
TTrack const *style{nullptr};
|
||||
bool filler{false};
|
||||
for (auto const &[other, point] : editor_track::connected_points(Track, From))
|
||||
{
|
||||
if (other->m_editorremoved)
|
||||
continue;
|
||||
if (other->eType == tt_Normal && std::find(Line.tracks.begin(), Line.tracks.end(), other) != Line.tracks.end())
|
||||
{
|
||||
Edit.capture(*other);
|
||||
editor_track::move_point(*other, point, To);
|
||||
continue;
|
||||
}
|
||||
filler = true;
|
||||
if (style == nullptr && other->eType == tt_Normal)
|
||||
style = other;
|
||||
}
|
||||
if (filler)
|
||||
{
|
||||
segment_data path;
|
||||
path.points[segment_data::point::start] = To;
|
||||
path.points[segment_data::point::end] = From;
|
||||
auto *piece{editor_track::create_path(style != nullptr ? *style : Track, path)};
|
||||
Edit.created.push_back(piece);
|
||||
Edit.changed.push_back(piece);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_mode::move_switch_paths(TTrack &Track, std::vector<segment_data> const &Paths, std::string &Status)
|
||||
{
|
||||
if (Paths.size() < 2 || Track.m_paths.size() < 2 || false == editor_track::main_axis(Track).has_value())
|
||||
return false;
|
||||
double low{0.0};
|
||||
double high{0.0};
|
||||
std::string reason;
|
||||
bool const online{switch_room(Track, low, high, reason)};
|
||||
if (false == online && false == reason.empty())
|
||||
{
|
||||
Status = reason;
|
||||
return false;
|
||||
}
|
||||
auto const ends{switch_end_moves(Track, Paths.front(), online, {low, high}, m_straights.tolerance.offset, Status)};
|
||||
if (false == ends.has_value())
|
||||
return false;
|
||||
auto const joints{editor_track::joints(Track)};
|
||||
std::vector<glm::dvec3> positions;
|
||||
for (auto const &joint : joints)
|
||||
positions.push_back(editor_track::point_position(Track, joint.own));
|
||||
std::vector<bool> handled(joints.size(), false);
|
||||
editor_track::edit edit;
|
||||
edit.capture(Track);
|
||||
auto const line{online ? editor_track::find_straight(Track, m_straights.tolerance) : editor_track::straight{}};
|
||||
for (auto const &end : *ends)
|
||||
{
|
||||
for (std::size_t i = 0; i < joints.size(); ++i)
|
||||
if (glm::distance(positions[i], end.from) < geometry::same_point)
|
||||
handled[i] = true;
|
||||
if (false == switch_vacate(Track, end.from, end.to, end.shift, line, edit, Status))
|
||||
return false;
|
||||
}
|
||||
Track.m_paths.assign(Paths.begin(), Paths.begin() + 2);
|
||||
auto const loose{switch_rejoin(Track, joints, handled, edit)};
|
||||
editor_track::commit(edit.changed);
|
||||
push_track_snapshot(std::move(edit.states), std::move(edit.created), std::move(edit.removed));
|
||||
if (false == loose.empty())
|
||||
Status = STR_C("Too far from the new ends of the switch, left unjoined: ") + loose;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_mode::slide_switch(TTrack &Track, double const Distance)
|
||||
{
|
||||
if (Track.m_paths.size() < 2 || std::abs(Distance) < 0.005)
|
||||
return false;
|
||||
auto const paths{editor_track::slid(Track, Distance)};
|
||||
if (paths.empty())
|
||||
return false;
|
||||
std::string status;
|
||||
if (false == move_switch_paths(Track, paths, status))
|
||||
{
|
||||
m_switch.status = status;
|
||||
ui()->set_status(status, true);
|
||||
return false;
|
||||
}
|
||||
switch_store::follow();
|
||||
records_to_history();
|
||||
straight_refresh();
|
||||
m_switch.status = status.empty() ? format(STR_C("Switch %s moved %.2f m along the straight"), Track.name().c_str(), Distance) : status;
|
||||
ui()->set_status(m_switch.status, false == status.empty());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool editor_mode::switch_slides(TTrack const &Track) const
|
||||
{
|
||||
return Track.eType == tt_Switch && Track.m_paths.size() == 2 && false == Track.m_editorremoved && false == has_controls(Track.m_paths.front());
|
||||
}
|
||||
|
||||
void editor_mode::switch_slide_start(TTrack &Track)
|
||||
{
|
||||
auto &slide{m_switch.slide};
|
||||
slide = {};
|
||||
std::string reason;
|
||||
if (false == switch_room(Track, slide.low, slide.high, reason))
|
||||
{
|
||||
if (false == reason.empty())
|
||||
ui()->set_status(reason, true);
|
||||
return;
|
||||
}
|
||||
auto const axis{editor_track::main_axis(Track)};
|
||||
auto const &mouse{ImGui::GetIO().MousePos};
|
||||
slide.active = true;
|
||||
slide.track = &Track;
|
||||
slide.pressed = {mouse.x, mouse.y};
|
||||
slide.grip = axis->along(cursor_level(axis->origin.y));
|
||||
auto const line{editor_track::find_straight(Track, m_straights.tolerance)};
|
||||
for (std::size_t i = 0; i < line.tracks.size(); ++i)
|
||||
{
|
||||
if (line.tracks[i] == &Track)
|
||||
continue;
|
||||
auto const &path{line.tracks[i]->m_paths[line.paths[i]]};
|
||||
for (auto const index : {segment_data::point::start, segment_data::point::end})
|
||||
slide.joints.push_back(axis->along(path.points[index]));
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::switch_slide_update()
|
||||
{
|
||||
auto &slide{m_switch.slide};
|
||||
if (false == slide.active)
|
||||
return;
|
||||
auto const axis{slide.track != nullptr && false == slide.track->m_editorremoved && slide.track->m_paths.size() >= 2 ? editor_track::main_axis(*slide.track) : std::nullopt};
|
||||
if (false == axis.has_value())
|
||||
{
|
||||
slide = {};
|
||||
return;
|
||||
}
|
||||
auto const length{axis->length};
|
||||
auto const distance{std::clamp(axis->along(cursor_level(axis->origin.y)) - slide.grip, slide.low, slide.high - length)};
|
||||
double best{1.0};
|
||||
double snapped{distance};
|
||||
for (auto const joint : slide.joints)
|
||||
for (auto const end : {0.0, length})
|
||||
if (auto const gap{std::abs(joint - (end + distance))}; gap < best && std::abs(joint - end) > 0.01)
|
||||
{
|
||||
best = gap;
|
||||
snapped = joint - end;
|
||||
}
|
||||
slide.distance = std::clamp(snapped, slide.low, slide.high - length);
|
||||
}
|
||||
|
||||
void editor_mode::switch_slide_finish()
|
||||
{
|
||||
auto &slide{m_switch.slide};
|
||||
if (false == slide.active)
|
||||
return;
|
||||
auto *track{slide.track};
|
||||
auto const distance{slide.distance};
|
||||
auto const &mouse{ImGui::GetIO().MousePos};
|
||||
auto const dragged{glm::length(glm::vec2{mouse.x, mouse.y} - slide.pressed) > kDragPixels};
|
||||
slide = {};
|
||||
if (track != nullptr && false == track->m_editorremoved && dragged)
|
||||
slide_switch(*track, distance);
|
||||
}
|
||||
|
||||
void editor_mode::draw_switch_slide() const
|
||||
{
|
||||
auto const &slide{m_switch.slide};
|
||||
if (false == slide.active || slide.track == nullptr || slide.track->m_paths.size() < 2)
|
||||
return;
|
||||
auto const &mouse{ImGui::GetIO().MousePos};
|
||||
if (glm::length(glm::vec2{mouse.x, mouse.y} - slide.pressed) <= kDragPixels)
|
||||
return;
|
||||
auto const paths{editor_track::slid(*slide.track, slide.distance)};
|
||||
if (paths.empty())
|
||||
return;
|
||||
screen_projection const projection;
|
||||
auto *drawlist{ImGui::GetBackgroundDrawList(ImGui::GetMainViewport())};
|
||||
for (auto const &path : paths)
|
||||
{
|
||||
bezier const curve{path};
|
||||
auto previous{curve.point(0.0)};
|
||||
for (int i = 1; i <= 24; ++i)
|
||||
{
|
||||
auto const next{curve.point(i / 24.0)};
|
||||
projection.line(drawlist, previous, next, overlay_color::marked, 3.0f);
|
||||
previous = next;
|
||||
}
|
||||
}
|
||||
ImVec2 at;
|
||||
auto const start{paths.front().points[segment_data::point::start]};
|
||||
if (false == projection.project(start, at))
|
||||
return;
|
||||
auto const km{alignment_store::chainage_at(start)};
|
||||
auto const label{km.has_value() ? format("%+.2f m km %.3f", slide.distance, *km / 1000.0) : format("%+.2f m", slide.distance)};
|
||||
auto const size{ImGui::CalcTextSize(label.c_str())};
|
||||
drawlist->AddRectFilled(ImVec2(at.x + 10.0f, at.y - 8.0f), ImVec2(at.x + 18.0f + size.x, at.y + 8.0f), IM_COL32(0, 0, 0, 180), 3.0f);
|
||||
drawlist->AddText(ImVec2(at.x + 14.0f, at.y - 8.0f), overlay_color::highlight, label.c_str());
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
132
application/editormodetrackbed.cpp
Normal file
132
application/editormodetrackbed.cpp
Normal file
@@ -0,0 +1,132 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "application/editormode.h"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "editor/editorGroundFollow.hpp"
|
||||
|
||||
#include "utilities/Globals.h"
|
||||
#include "world/Track.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using geometry::plan_distance;
|
||||
using geometry::plan_of;
|
||||
|
||||
} // namespace
|
||||
|
||||
glm::dvec3 editor_mode::cursor_terrain(double const Height)
|
||||
{
|
||||
auto point{cursor_level(Height)};
|
||||
for (int i = 0; i < 5 && std::isfinite(point.x) && std::isfinite(point.z); ++i)
|
||||
{
|
||||
std::vector<char> found;
|
||||
auto ground{ground_heights({glm::dvec3{point.x, std::max(point.y, static_cast<double>(Global.pCamera.Pos.y)), point.z}}, true, &found).front()};
|
||||
if (found[0] == 0)
|
||||
ground = nearby_ground(point);
|
||||
if (std::isnan(ground))
|
||||
return point;
|
||||
auto const next{cursor_level(ground)};
|
||||
auto const settled{plan_distance(next, point) < 0.01};
|
||||
point = {next.x, ground, next.z};
|
||||
if (settled)
|
||||
break;
|
||||
}
|
||||
return point;
|
||||
}
|
||||
|
||||
double editor_mode::nearby_ground(glm::dvec3 const &Point) const
|
||||
{
|
||||
auto const ceiling{std::max(Point.y, static_cast<double>(Global.pCamera.Pos.y))};
|
||||
for (double const radius : {10.0, 25.0, 50.0, 100.0, 200.0, 400.0})
|
||||
{
|
||||
std::vector<glm::dvec3> probes;
|
||||
for (int i = 0; i < 12; ++i)
|
||||
{
|
||||
auto const angle{glm::two_pi<double>() * i / 12.0};
|
||||
probes.push_back({Point.x + radius * std::cos(angle), ceiling, Point.z + radius * std::sin(angle)});
|
||||
}
|
||||
std::vector<char> found;
|
||||
auto const heights{ground_heights(probes, true, &found)};
|
||||
double sum{0.0};
|
||||
int count{0};
|
||||
for (std::size_t i = 0; i < probes.size(); ++i)
|
||||
if (found[i] != 0)
|
||||
{
|
||||
sum += heights[i];
|
||||
++count;
|
||||
}
|
||||
if (count > 0)
|
||||
return sum / count;
|
||||
}
|
||||
return std::numeric_limits<double>::quiet_NaN();
|
||||
}
|
||||
|
||||
std::pair<double, double> editor_mode::trackbed(TTrack const *Style)
|
||||
{
|
||||
return Style != nullptr ? profile_ballast(*Style) : std::pair<double, double>{0.2, 0.5 * 1.435 + 0.5 + 1.1};
|
||||
}
|
||||
|
||||
std::vector<double> editor_mode::bed_heights(std::vector<glm::dvec3> const &Points, std::vector<glm::dvec2> const &Directions, TTrack const *Style) const
|
||||
{
|
||||
auto const [depth, half]{trackbed(Style)};
|
||||
std::vector<glm::dvec3> probes;
|
||||
probes.reserve(Points.size() * 3);
|
||||
for (std::size_t i = 0; i < Points.size(); ++i)
|
||||
{
|
||||
auto const &direction{Directions[i]};
|
||||
auto const side{glm::length(direction) > 1e-6 ? glm::normalize(glm::dvec2{-direction.y, direction.x}) * half : glm::dvec2{0.0}};
|
||||
probes.push_back(Points[i]);
|
||||
probes.push_back(Points[i] + glm::dvec3{side.x, 0.0, side.y});
|
||||
probes.push_back(Points[i] - glm::dvec3{side.x, 0.0, side.y});
|
||||
}
|
||||
std::vector<char> found;
|
||||
auto const heights{ground_heights(probes, true, &found)};
|
||||
std::vector<double> result(Points.size(), std::numeric_limits<double>::quiet_NaN());
|
||||
for (std::size_t i = 0; i < Points.size(); ++i)
|
||||
{
|
||||
auto const axis{found[3 * i] != 0 ? heights[3 * i] : std::numeric_limits<double>::quiet_NaN()};
|
||||
auto toe{std::numeric_limits<double>::infinity()};
|
||||
for (std::size_t j = 3 * i + 1; j < 3 * i + 3; ++j)
|
||||
if (found[j] != 0)
|
||||
toe = std::min(toe, heights[j]);
|
||||
if (std::isfinite(toe))
|
||||
result[i] = std::isnan(axis) ? toe + depth : std::max(toe + depth, axis);
|
||||
else if (false == std::isnan(axis))
|
||||
result[i] = axis + depth;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double editor_mode::bed_height(glm::dvec3 const &Point, glm::dvec2 const &Direction, TTrack const *Style) const
|
||||
{
|
||||
auto const height{bed_heights({Point}, {Direction}, Style).front()};
|
||||
if (false == std::isnan(height))
|
||||
return height;
|
||||
auto const nearby{nearby_ground(Point)};
|
||||
return std::isnan(nearby) ? Point.y : nearby + trackbed(Style).first;
|
||||
}
|
||||
|
||||
void editor_mode::follow_ground(std::vector<segment_data> &Pieces, double const Height0, double const Grade0, double const Height1, double const Grade1, TTrack const *Style) const
|
||||
{
|
||||
ground_follow::follow(Pieces, {Height0, Grade0, Height1, Grade1}, [&](std::vector<glm::dvec3> const &Points, std::vector<glm::dvec2> const &Directions) { return bed_heights(Points, Directions, Style); });
|
||||
}
|
||||
|
||||
glm::dvec3 editor_mode::lay_bed(glm::dvec3 const &Point)
|
||||
{
|
||||
auto const &tool{m_lay};
|
||||
TTrack const *style{tool.start.track != nullptr ? tool.start.track : tool.copy_selected ? selected_track() : nullptr};
|
||||
return {Point.x, bed_height(Point, tool.points.empty() ? glm::dvec2{0.0} : plan_of(Point - tool.points.back()), style), Point.z};
|
||||
}
|
||||
729
application/editormodetrackgizmo.cpp
Normal file
729
application/editormodetrackgizmo.cpp
Normal file
@@ -0,0 +1,729 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "application/editormode.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
|
||||
#include "utilities/Globals.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Track.h"
|
||||
|
||||
#include "imgui/imgui.h"
|
||||
#include "imgui/ImGuizmo.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using geometry::bezier;
|
||||
using geometry::level;
|
||||
using geometry::plan_of;
|
||||
using geometry::turned;
|
||||
|
||||
bool mouse_along(glm::dvec3 const &Origin, glm::dvec3 const &Axis, double &Along)
|
||||
{
|
||||
screen_projection const projection;
|
||||
glm::dvec3 origin;
|
||||
glm::dvec3 direction;
|
||||
projection.ray(ImGui::GetIO().MousePos, origin, direction);
|
||||
auto const b{glm::dot(Axis, direction)};
|
||||
auto const denominator{1.0 - b * b};
|
||||
if (denominator < 1e-6)
|
||||
return false;
|
||||
auto const w{Origin - origin};
|
||||
Along = (b * glm::dot(direction, w) - glm::dot(Axis, w)) / denominator;
|
||||
return true;
|
||||
}
|
||||
|
||||
glm::mat4 gizmo_along(glm::dvec3 const &Position, glm::dvec3 const &Axis)
|
||||
{
|
||||
auto gizmo{glm::translate(glm::mat4(1.0f), glm::vec3(Position))};
|
||||
if (glm::length(Axis) < 1e-9)
|
||||
return gizmo;
|
||||
auto const z{glm::normalize(Axis)};
|
||||
auto x{glm::cross(glm::dvec3{0.0, 1.0, 0.0}, z)};
|
||||
x = glm::length(x) > 1e-6 ? glm::normalize(x) : glm::dvec3{1.0, 0.0, 0.0};
|
||||
auto const y{glm::cross(z, x)};
|
||||
gizmo[0] = glm::vec4(glm::vec3(x), 0.0f);
|
||||
gizmo[1] = glm::vec4(glm::vec3(y), 0.0f);
|
||||
gizmo[2] = glm::vec4(glm::vec3(z), 0.0f);
|
||||
return gizmo;
|
||||
}
|
||||
|
||||
void add_once(std::vector<TTrack *> &Tracks, TTrack *Track)
|
||||
{
|
||||
if (std::find(Tracks.begin(), Tracks.end(), Track) == Tracks.end())
|
||||
Tracks.emplace_back(Track);
|
||||
}
|
||||
|
||||
bool editable(TTrack const &Track)
|
||||
{
|
||||
std::string reason;
|
||||
return editor_track::can_edit_geometry(Track, reason);
|
||||
}
|
||||
|
||||
double snapped_grade(double const Grade)
|
||||
{
|
||||
return std::round(Grade * 1000.0) / 1000.0;
|
||||
}
|
||||
|
||||
double snapped_cant(double const Roll0, double const Roll)
|
||||
{
|
||||
auto const cant{std::round(1500.0 * std::sin(glm::radians(Roll0 + Roll)) / 5.0) * 5.0 / 1500.0};
|
||||
return glm::degrees(std::asin(std::clamp(cant, -1.0, 1.0))) - Roll0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
editor_mode::gizmo_operation editor_mode::track_gizmo_operation(TTrack const &Track, bool const Pointmode) const
|
||||
{
|
||||
if (m_gizmo_op == gizmo_operation::translate || (Pointmode && false == editor_track::is_end(m_track_point.kind)))
|
||||
return m_gizmo_op;
|
||||
if (m_gizmo_op == gizmo_operation::rotate)
|
||||
return gizmo_operation::rotate;
|
||||
return editor_track::stretch_end(Track, Pointmode ? m_track_point : editor_track::point_ref{}).has_value() ? gizmo_operation::scale : gizmo_operation::translate;
|
||||
}
|
||||
|
||||
glm::mat4 editor_mode::track_gizmo_frame(TTrack const &Track, bool const Pointmode, gizmo_operation const Operation) const
|
||||
{
|
||||
auto anchor{Pointmode ? editor_track::point_position(Track, m_track_point) : editor_track::pivot(Track)};
|
||||
bool const controlmode{Pointmode && false == editor_track::is_end(m_track_point.kind)};
|
||||
glm::dvec3 axis{0.0};
|
||||
if (Operation == gizmo_operation::scale)
|
||||
{
|
||||
if (controlmode)
|
||||
axis = editor_track::control_vector(Track, m_track_point);
|
||||
else if (auto const end{editor_track::stretch_end(Track, Pointmode ? m_track_point : editor_track::point_ref{})})
|
||||
std::tie(anchor, axis) = editor_track::end_axis(Track.m_paths[end->path], end->atend);
|
||||
}
|
||||
else if (Operation == gizmo_operation::rotate)
|
||||
{
|
||||
if (controlmode)
|
||||
axis = level(editor_track::control_vector(Track, m_track_point));
|
||||
else if (Pointmode)
|
||||
axis = level(bezier{Track.m_paths[m_track_point.path]}.first(m_track_point.kind == editor_track::point_kind::start ? 0.0 : 1.0));
|
||||
else
|
||||
{
|
||||
auto const middle{editor_track::middle(Track)};
|
||||
anchor = middle.point;
|
||||
axis = level(middle.tangent);
|
||||
}
|
||||
}
|
||||
return gizmo_along(anchor - glm::dvec3{GfxRenderer->Camera_Position()}, axis);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_hold()
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
drag.held.clear();
|
||||
for (auto *track : m_track_drag)
|
||||
{
|
||||
if (drag.operation == gizmo_operation::scale && false == drag.control && track == selected_track())
|
||||
continue;
|
||||
auto held{editor_track::hold_controls(*track)};
|
||||
if (false == held.empty())
|
||||
drag.held.emplace_back(track, std::move(held));
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_settle()
|
||||
{
|
||||
for (auto &[track, held] : m_track_gizmo_drag.held)
|
||||
editor_track::settle_controls(*track, held);
|
||||
m_track_gizmo_drag.held.clear();
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_conform(TTrack &Track) const
|
||||
{
|
||||
if (Track.eType != tt_Switch)
|
||||
return;
|
||||
editor_track::conform_switch(Track);
|
||||
editor_track::follow(Track, m_track_joints);
|
||||
editor_track::follow_grade(Track, m_track_joints);
|
||||
}
|
||||
|
||||
bool editor_mode::track_gizmo_clustered(TTrack const *Track) const
|
||||
{
|
||||
return std::any_of(m_track_gizmo_drag.switches.begin(), m_track_gizmo_drag.switches.end(), [&](auto const &Link) { return std::any_of(Link.cluster.begin(), Link.cluster.end(), [&](auto const &Member) { return Member.first == Track; }); });
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_stretch(TTrack &Track, double const Length)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
auto const pieces{editor_track::stretched(drag.paths[drag.path], drag.atend, Length)};
|
||||
if (pieces.empty())
|
||||
return;
|
||||
auto paths{drag.paths};
|
||||
paths.erase(paths.begin() + drag.path);
|
||||
paths.insert(paths.begin() + drag.path, pieces.begin(), pieces.end());
|
||||
Track.m_paths = std::move(paths);
|
||||
drag.pieces = pieces.size();
|
||||
drag.stretch = Length;
|
||||
if (drag.point.valid())
|
||||
m_track_point = editor_track::stretch_remap(drag.point, drag.path, drag.pieces);
|
||||
auto joints{m_track_joints};
|
||||
for (auto &joint : joints)
|
||||
joint.own = editor_track::stretch_remap(joint.own, drag.path, drag.pieces);
|
||||
editor_track::follow(Track, joints);
|
||||
track_gizmo_spill(Track, pieces.size());
|
||||
m_track_dirty = true;
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_spill(TTrack &Track, std::size_t const Pieces)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
std::vector<segment_data> rest;
|
||||
if (Track.eType == tt_Normal && Pieces > 1)
|
||||
{
|
||||
auto const first{Track.m_paths.begin() + drag.path};
|
||||
auto const keep{drag.atend ? first : first + (Pieces - 1)};
|
||||
for (auto piece = first; piece != first + Pieces; ++piece)
|
||||
if (piece != keep)
|
||||
rest.push_back(*piece);
|
||||
auto const kept{*keep};
|
||||
Track.m_paths.erase(first, first + Pieces);
|
||||
Track.m_paths.insert(Track.m_paths.begin() + drag.path, kept);
|
||||
if (drag.point.valid())
|
||||
m_track_point = drag.point;
|
||||
}
|
||||
while (drag.extra.size() > rest.size())
|
||||
{
|
||||
auto *surplus{drag.extra.back()};
|
||||
drag.extra.pop_back();
|
||||
std::erase(m_track_drag, surplus);
|
||||
editor_track::retire(*surplus);
|
||||
}
|
||||
for (std::size_t i = 0; i < rest.size(); ++i)
|
||||
{
|
||||
if (i < drag.extra.size())
|
||||
{
|
||||
drag.extra[i]->m_paths = {rest[i]};
|
||||
continue;
|
||||
}
|
||||
auto *created{editor_track::create_path(Track, rest[i])};
|
||||
drag.extra.push_back(created);
|
||||
m_track_drag.push_back(created);
|
||||
editor_track::commit({created});
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_control(TTrack &Track, glm::dvec3 Control)
|
||||
{
|
||||
auto const &drag{m_track_gizmo_drag};
|
||||
if (drag.fixed != glm::dvec3{})
|
||||
Control = drag.fixed * std::max(0.5, glm::dot(Control, drag.fixed));
|
||||
if (glm::length(Control) < 0.5)
|
||||
Control = glm::length(Control) > 1e-9 ? glm::normalize(Control) * 0.5 : drag.axis * 0.5;
|
||||
editor_track::move_point(Track, drag.point, editor_track::point_position(Track, editor_track::end_of(drag.point)) + Control);
|
||||
editor_track::align_controls(Track, drag.point, drag.partners);
|
||||
track_gizmo_conform(Track);
|
||||
m_track_dirty = true;
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_point(TTrack &Track, glm::dvec3 const &Position)
|
||||
{
|
||||
editor_track::move_point(Track, m_track_point, Position);
|
||||
track_gizmo_conform(Track);
|
||||
auto const moved{editor_track::point_position(Track, m_track_point)};
|
||||
for (auto const &[track, point] : m_track_drag_points)
|
||||
if (false == track_gizmo_clustered(track))
|
||||
editor_track::move_point(*track, point, moved);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_carry(track_gizmo_drag::switch_joint &Link, glm::dvec3 const &Point, glm::dvec3 const &From, glm::dvec3 const &To, double const Grade)
|
||||
{
|
||||
auto const c{glm::dot(From, To)};
|
||||
auto const s{From.x * To.z - From.z * To.x};
|
||||
auto const turn = [&](glm::dvec3 const &Vector) { return glm::dvec3{Vector.x * c - Vector.z * s, Vector.y, Vector.z * c + Vector.x * s}; };
|
||||
auto const tilt = [&](glm::dvec3 const &Vector) { return (Vector.x * To.x + Vector.z * To.z) * Grade; };
|
||||
for (auto &[member, paths] : Link.cluster)
|
||||
{
|
||||
member->m_paths = paths;
|
||||
for (auto &path : member->m_paths)
|
||||
{
|
||||
for (auto const index : {segment_data::point::start, segment_data::point::end})
|
||||
{
|
||||
auto const moved{turn(path.points[index] - Link.point)};
|
||||
path.points[index] = Point + moved + glm::dvec3{0.0, tilt(moved), 0.0};
|
||||
}
|
||||
for (auto const index : {segment_data::point::control1, segment_data::point::control2})
|
||||
{
|
||||
auto &control{path.points[index]};
|
||||
if (control == glm::dvec3{})
|
||||
continue;
|
||||
control = turn(control);
|
||||
control.y += tilt(control);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_roll_switch(track_gizmo_drag::switch_joint const &Link, double const Roll)
|
||||
{
|
||||
auto &main{Link.track->m_paths[0]};
|
||||
bool const start{Link.theirs.kind == editor_track::point_kind::start};
|
||||
auto value{start ? -Roll : Roll};
|
||||
if (Link.theirs.path != 0)
|
||||
{
|
||||
auto const t{start ? 0.0 : 1.0};
|
||||
if (glm::dot(plan_of(bezier{Link.track->m_paths[Link.theirs.path]}.first(t)), plan_of(bezier{main}.first(t))) < 0.0)
|
||||
value = -value;
|
||||
}
|
||||
main.rolls = {static_cast<float>(value), static_cast<float>(value)};
|
||||
editor_track::conform_switch(*Link.track);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_switches(TTrack &Track)
|
||||
{
|
||||
for (auto &link : m_track_gizmo_drag.switches)
|
||||
{
|
||||
if (link.own.path >= static_cast<int>(Track.m_paths.size()))
|
||||
continue;
|
||||
auto const point{editor_track::point_position(Track, link.own)};
|
||||
auto const tangent{editor_track::outward(Track, link.own)};
|
||||
auto const roll{editor_track::joint_roll(Track, link.own)};
|
||||
auto const from{level(link.tangent)};
|
||||
auto const to{level(tangent)};
|
||||
auto const grade{geometry::grade_of(tangent) - geometry::grade_of(link.tangent)};
|
||||
bool const changed{glm::distance(point, link.point) > 1e-9 || glm::distance(from, to) > 1e-12 || std::abs(grade) > 1e-12 || std::abs(roll - link.roll) > 1e-6};
|
||||
if (false == changed && false == link.moved)
|
||||
continue;
|
||||
link.moved = true;
|
||||
track_gizmo_carry(link, point, from, to, grade);
|
||||
if (std::abs(roll - link.roll) > 1e-6)
|
||||
track_gizmo_roll_switch(link, roll);
|
||||
for (auto const &[member, joints] : link.joints)
|
||||
{
|
||||
editor_track::follow(*member, joints);
|
||||
editor_track::follow_grade(*member, joints);
|
||||
editor_track::match_rolls(*member, joints);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_take_joints(TTrack &Track)
|
||||
{
|
||||
m_track_joints = editor_track::joints(Track);
|
||||
for (auto const &joint : m_track_joints)
|
||||
add_once(m_track_drag, joint.other);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_take_points(TTrack &Track)
|
||||
{
|
||||
for (auto const &connection : editor_track::connected_points(Track, editor_track::point_position(Track, m_track_point)))
|
||||
{
|
||||
if (false == editable(*connection.first))
|
||||
continue;
|
||||
m_track_drag_points.emplace_back(connection);
|
||||
add_once(m_track_drag, connection.first);
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_take_partners(TTrack &Track)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
auto const end{editor_track::end_of(m_track_point)};
|
||||
for (auto const &partner : editor_track::partners(Track, end))
|
||||
{
|
||||
auto const &[other, theirs]{partner};
|
||||
bool const own{other == &Track};
|
||||
bool const fixed{own ? m_track_point.path != 0 : other->eType != tt_Normal || false == editable(*other)};
|
||||
if (fixed)
|
||||
{
|
||||
auto const &path{other->m_paths[theirs.path]};
|
||||
bool const start{theirs.kind == editor_track::point_kind::start};
|
||||
auto const control{path.points[start ? segment_data::point::control1 : segment_data::point::control2]};
|
||||
auto const chord{path.points[start ? segment_data::point::end : segment_data::point::start] - path.points[start ? segment_data::point::start : segment_data::point::end]};
|
||||
auto const along{control != glm::dvec3{} ? control : chord};
|
||||
if (glm::length(along) > 1e-9)
|
||||
drag.fixed = (own && theirs.kind == end.kind ? 1.0 : -1.0) * glm::normalize(along);
|
||||
continue;
|
||||
}
|
||||
if (false == m_track_drag_connected && false == own)
|
||||
continue;
|
||||
drag.partners.push_back(partner);
|
||||
add_once(m_track_drag, other);
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_take_switch_joints(TTrack &Track, bool const Controlmode)
|
||||
{
|
||||
auto const edited{editor_track::point_position(Track, editor_track::end_of(m_track_point))};
|
||||
for (auto const &joint : editor_track::joints(Track))
|
||||
{
|
||||
if (Controlmode && glm::distance(editor_track::point_position(Track, joint.own), edited) < geometry::same_point)
|
||||
continue;
|
||||
m_track_joints.push_back(joint);
|
||||
add_once(m_track_drag, joint.other);
|
||||
}
|
||||
}
|
||||
|
||||
editor_mode::track_gizmo_drag::switch_joint editor_mode::track_gizmo_link(TTrack &Track, editor_track::point_ref const &Own, std::pair<TTrack *, editor_track::point_ref> const &Connection, std::function<bool(TTrack const *)> const &Movable)
|
||||
{
|
||||
track_gizmo_drag::switch_joint link{Connection.first, Own, Connection.second, editor_track::point_position(Track, Own), editor_track::outward(Track, Own), editor_track::joint_roll(Track, Own)};
|
||||
std::vector<TTrack *> members{Connection.first};
|
||||
for (std::size_t k = 0; k < members.size(); ++k)
|
||||
for (auto *next : editor_track::neighbours(*members[k]))
|
||||
if (Movable(next) && std::find(members.begin(), members.end(), next) == members.end())
|
||||
members.push_back(next);
|
||||
for (auto *member : members)
|
||||
{
|
||||
link.cluster.emplace_back(member, std::vector<segment_data>{});
|
||||
auto joints{editor_track::joints(*member)};
|
||||
std::erase_if(joints, [&](editor_track::joint const &Joint) { return Joint.other == &Track; });
|
||||
add_once(m_track_drag, member);
|
||||
for (auto const &joint : joints)
|
||||
add_once(m_track_drag, joint.other);
|
||||
link.joints.emplace_back(member, std::move(joints));
|
||||
}
|
||||
return link;
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_take_switches(TTrack &Track)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
auto const movable = [&](TTrack const *Other) { return Other != &Track && Other->eType == tt_Switch && Other->m_paths.size() == 2 && editable(*Other) && false == track_gizmo_clustered(Other); };
|
||||
for (int i = 0; i < static_cast<int>(Track.m_paths.size()); ++i)
|
||||
for (auto const kind : {editor_track::point_kind::start, editor_track::point_kind::end})
|
||||
{
|
||||
editor_track::point_ref const own{i, kind};
|
||||
for (auto const &connection : editor_track::connected_points(Track, editor_track::point_position(Track, own)))
|
||||
if (movable(connection.first))
|
||||
drag.switches.push_back(track_gizmo_link(Track, own, connection, movable));
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_measure_rotation(TTrack &Track, bool const Pointmode)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
auto const frame{track_gizmo_frame(Track, Pointmode, gizmo_operation::rotate)};
|
||||
drag.basis = glm::dmat3(glm::mat3(frame));
|
||||
drag.pivot = glm::dvec3{GfxRenderer->Camera_Position()} + glm::dvec3(frame[3]);
|
||||
double along{0.0};
|
||||
for (auto const &path : Track.m_paths)
|
||||
{
|
||||
drag.chainages.push_back(along);
|
||||
drag.lengths.push_back(bezier{path}.plan_length());
|
||||
along += drag.lengths.back();
|
||||
}
|
||||
auto const end{editor_track::switch_main(Track, editor_track::end_of(drag.point))};
|
||||
if (Track.eType == tt_Switch && false == Track.m_paths.empty())
|
||||
{
|
||||
drag.pivot_chainage = editor_track::switch_station(Track, drag.pivot);
|
||||
drag.roll0 = end.valid() && end.kind == editor_track::point_kind::end ? Track.m_paths[0].rolls[1] : Track.m_paths[0].rolls[0];
|
||||
}
|
||||
else if (end.valid() && end.path < static_cast<int>(drag.chainages.size()))
|
||||
{
|
||||
drag.pivot_chainage = drag.chainages[end.path] + (end.kind == editor_track::point_kind::end ? drag.lengths[end.path] : 0.0);
|
||||
drag.roll0 = end.kind == editor_track::point_kind::start ? Track.m_paths[end.path].rolls[0] : Track.m_paths[end.path].rolls[1];
|
||||
}
|
||||
else
|
||||
{
|
||||
drag.pivot_chainage = along * 0.5;
|
||||
drag.roll0 = Track.m_paths.empty() ? 0.0 : Track.m_paths.front().rolls[0];
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_measure(TTrack &Track, bool const Pointmode, gizmo_operation const Operation)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
if (drag.control)
|
||||
{
|
||||
drag.origin = editor_track::point_position(Track, m_track_point);
|
||||
auto const control{editor_track::control_vector(Track, m_track_point)};
|
||||
drag.length = glm::length(control);
|
||||
drag.axis = drag.length > 1e-9 ? control / drag.length : glm::dvec3{0.0, 0.0, 1.0};
|
||||
}
|
||||
else if (auto const end{Operation == gizmo_operation::scale ? editor_track::stretch_end(Track, drag.point) : std::nullopt})
|
||||
{
|
||||
drag.path = end->path;
|
||||
drag.atend = end->atend;
|
||||
drag.paths = Track.m_paths;
|
||||
std::tie(drag.origin, drag.axis) = editor_track::end_axis(Track.m_paths[drag.path], drag.atend);
|
||||
drag.length = bezier{Track.m_paths[drag.path]}.plan_length();
|
||||
}
|
||||
drag.stretch = drag.length;
|
||||
if (Operation == gizmo_operation::rotate)
|
||||
track_gizmo_measure_rotation(Track, Pointmode);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_begin(TTrack &Track, bool const Pointmode, gizmo_operation const Operation)
|
||||
{
|
||||
bool const controlmode{Pointmode && false == editor_track::is_end(m_track_point.kind)};
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
m_track_gizmo_using = true;
|
||||
m_track_pivot = editor_track::pivot(Track);
|
||||
m_track_drag = {&Track};
|
||||
m_track_drag_points.clear();
|
||||
m_track_joints.clear();
|
||||
m_track_snap = {};
|
||||
drag = {};
|
||||
drag.operation = Operation;
|
||||
drag.control = controlmode;
|
||||
drag.point = Pointmode ? m_track_point : editor_track::point_ref{};
|
||||
bool const whole{false == Pointmode || (false == controlmode && Operation != gizmo_operation::translate)};
|
||||
if (whole && m_track_drag_connected)
|
||||
track_gizmo_take_joints(Track);
|
||||
if (Pointmode && false == controlmode && Operation == gizmo_operation::translate && m_track_drag_connected)
|
||||
track_gizmo_take_points(Track);
|
||||
if (controlmode)
|
||||
track_gizmo_take_partners(Track);
|
||||
if (false == whole && Track.eType == tt_Switch && m_track_drag_connected)
|
||||
track_gizmo_take_switch_joints(Track, controlmode);
|
||||
if (m_track_drag_connected)
|
||||
track_gizmo_take_switches(Track);
|
||||
std::vector<std::pair<TTrack *, editor_track::state>> states;
|
||||
for (auto *dragged : m_track_drag)
|
||||
states.emplace_back(dragged, editor_track::capture(*dragged));
|
||||
push_track_snapshot(std::move(states));
|
||||
records_watch_begin();
|
||||
track_gizmo_hold();
|
||||
for (auto &link : drag.switches)
|
||||
for (auto &[member, paths] : link.cluster)
|
||||
paths = member->m_paths;
|
||||
track_gizmo_measure(Track, Pointmode, Operation);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_turn_direction(TTrack &Track, double const Angle)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
auto target{Angle};
|
||||
if (Global.ctrlState)
|
||||
target = glm::radians(std::round(glm::degrees(target) / 5.0) * 5.0);
|
||||
auto const step{std::remainder(target - drag.angle, 2.0 * glm::pi<double>())};
|
||||
if (std::abs(step) < 1e-9)
|
||||
return;
|
||||
drag.angle += step;
|
||||
if (drag.control)
|
||||
{
|
||||
auto const control{editor_track::control_vector(Track, drag.point)};
|
||||
auto const plan{turned(plan_of(control), -step)};
|
||||
track_gizmo_control(Track, {plan.x, control.y, plan.y});
|
||||
return;
|
||||
}
|
||||
editor_track::rotate_y(Track, drag.pivot, step);
|
||||
editor_track::follow(Track, m_track_joints);
|
||||
m_track_dirty = true;
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_turn_grade(TTrack &Track, double const Angle)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
auto target{-std::tan(std::clamp(Angle, -1.4, 1.4))};
|
||||
if (Global.ctrlState)
|
||||
target = snapped_grade(target);
|
||||
auto const step{target - drag.grade};
|
||||
if (std::abs(step) < 1e-9)
|
||||
return;
|
||||
drag.grade = target;
|
||||
if (drag.control)
|
||||
{
|
||||
auto const control{editor_track::control_vector(Track, drag.point)};
|
||||
auto const run{glm::length(plan_of(control))};
|
||||
auto const along{glm::dot(level(control), drag.basis[2])};
|
||||
track_gizmo_control(Track, {control.x, control.y + step * run * (along >= 0.0 ? 1.0 : -1.0), control.z});
|
||||
return;
|
||||
}
|
||||
auto const count{editor_track::driven_paths(Track)};
|
||||
for (std::size_t i = 0; i < count && i < drag.chainages.size(); ++i)
|
||||
{
|
||||
auto &path{Track.m_paths[i]};
|
||||
auto const from{drag.chainages[i] - drag.pivot_chainage};
|
||||
auto const to{from + drag.lengths[i]};
|
||||
path.points[segment_data::point::start].y += step * from;
|
||||
path.points[segment_data::point::end].y += step * to;
|
||||
auto &control1{path.points[segment_data::point::control1]};
|
||||
auto &control2{path.points[segment_data::point::control2]};
|
||||
control1.y += step * glm::length(plan_of(control1));
|
||||
control2.y -= step * glm::length(plan_of(control2));
|
||||
}
|
||||
editor_track::conform_switch(Track);
|
||||
editor_track::follow(Track, m_track_joints);
|
||||
editor_track::follow_grade(Track, m_track_joints);
|
||||
m_track_dirty = true;
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_turn_cant(TTrack &Track, double const Angle)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
auto const end{editor_track::end_of(drag.point)};
|
||||
auto const canted{editor_track::switch_main(Track, end)};
|
||||
auto target{glm::degrees(drag.point.kind == editor_track::point_kind::control2 ? -Angle : Angle)};
|
||||
if (Global.ctrlState)
|
||||
target = snapped_cant(drag.roll0, target);
|
||||
auto const step{static_cast<float>(target - drag.roll)};
|
||||
if (std::abs(step) < 1e-6f)
|
||||
return;
|
||||
drag.roll = target;
|
||||
for (int i = 0; i < static_cast<int>(Track.m_paths.size()); ++i)
|
||||
{
|
||||
auto &path{Track.m_paths[i]};
|
||||
if (false == canted.valid() || (i == canted.path && canted.kind == editor_track::point_kind::start))
|
||||
path.rolls[0] += step;
|
||||
if (false == canted.valid() || (i == canted.path && canted.kind == editor_track::point_kind::end))
|
||||
path.rolls[1] += step;
|
||||
}
|
||||
track_gizmo_conform(Track);
|
||||
editor_track::match_rolls(Track, m_track_joints);
|
||||
for (auto const &[other, theirs] : drag.partners)
|
||||
if (other != &Track)
|
||||
editor_track::roll_joined(*other, theirs, static_cast<float>(editor_track::joint_roll(Track, end)));
|
||||
m_track_dirty = true;
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_turn(TTrack &Track, int const Axis, double const Angle)
|
||||
{
|
||||
auto const &drag{m_track_gizmo_drag};
|
||||
if (drag.control && drag.fixed != glm::dvec3{})
|
||||
return;
|
||||
if (Axis == 1)
|
||||
track_gizmo_turn_direction(Track, Angle);
|
||||
else if (Axis == 0)
|
||||
track_gizmo_turn_grade(Track, Angle);
|
||||
else
|
||||
track_gizmo_turn_cant(Track, Angle);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_length(TTrack &Track, double Length)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
Length = std::max(drag.control ? 0.5 : 1.0, Length);
|
||||
if (std::abs(Length - drag.stretch) < 1e-6)
|
||||
return;
|
||||
if (drag.control)
|
||||
{
|
||||
drag.stretch = Length;
|
||||
track_gizmo_control(Track, drag.axis * Length);
|
||||
}
|
||||
else if (drag.path >= 0)
|
||||
track_gizmo_stretch(Track, Length);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_end(TTrack &Track)
|
||||
{
|
||||
if (m_track_snap.track != nullptr && m_track_gizmo_drag.operation == gizmo_operation::translate && m_track_point.valid())
|
||||
editor_track::snap_point(Track, m_track_point, m_track_snap, m_track_align_tangent);
|
||||
track_gizmo_settle();
|
||||
m_track_dirty = true;
|
||||
m_track_snap = {};
|
||||
commit_track_drag(true);
|
||||
if (false == m_track_gizmo_drag.extra.empty() && false == m_history.empty())
|
||||
{
|
||||
auto &created{m_history.back().created};
|
||||
created.insert(created.end(), m_track_gizmo_drag.extra.begin(), m_track_gizmo_drag.extra.end());
|
||||
m_track_gizmo_drag.extra.clear();
|
||||
}
|
||||
records_watch_end();
|
||||
m_track_gizmo_using = false;
|
||||
m_track_drag.clear();
|
||||
m_track_drag_points.clear();
|
||||
m_track_joints.clear();
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_rotated(TTrack &Track)
|
||||
{
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
auto const turn{glm::transpose(drag.basis) * glm::dmat3(glm::mat3(m_track_gizmo))};
|
||||
std::array<double, 3> const angles{std::atan2(turn[1][2], turn[1][1]), std::atan2(turn[2][0], turn[0][0]), std::atan2(turn[0][1], turn[0][0])};
|
||||
if (drag.ring < 0)
|
||||
{
|
||||
auto const largest{std::max_element(angles.begin(), angles.end(), [](double const A, double const B) { return std::abs(A) < std::abs(B); })};
|
||||
if (std::abs(*largest) > 1e-5)
|
||||
drag.ring = static_cast<int>(largest - angles.begin());
|
||||
}
|
||||
if (drag.ring >= 0)
|
||||
track_gizmo_turn(Track, drag.ring, angles[drag.ring]);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_scaled(TTrack &Track)
|
||||
{
|
||||
auto const &drag{m_track_gizmo_drag};
|
||||
double along{0.0};
|
||||
if (false == mouse_along(drag.origin, drag.axis, along))
|
||||
return;
|
||||
auto length{drag.length + along - drag.grabbed};
|
||||
if (Global.ctrlState && m_gizmo_snap > 0.0f)
|
||||
length = std::round(length / m_gizmo_snap) * m_gizmo_snap;
|
||||
track_gizmo_length(Track, length);
|
||||
}
|
||||
|
||||
void editor_mode::track_gizmo_moved_point(TTrack &Track, glm::dvec3 const &Position)
|
||||
{
|
||||
track_gizmo_point(Track, Position);
|
||||
m_track_dirty = true;
|
||||
if (false == editor_track::is_end(m_track_point.kind) || false == m_track_drag_points.empty())
|
||||
return;
|
||||
std::vector<TTrack const *> const exclude(m_track_drag.begin(), m_track_drag.end());
|
||||
screen_projection const projection;
|
||||
ImVec2 screen;
|
||||
auto const position{editor_track::point_position(Track, m_track_point)};
|
||||
m_track_snap = projection.project(position, screen) ? snap_free_end(position, {screen.x, screen.y}, &Track, Track.iCategoryFlag & 15, exclude) : editor_track::snap_target{};
|
||||
}
|
||||
|
||||
void editor_mode::render_track_gizmo()
|
||||
{
|
||||
auto *track = selected_track();
|
||||
if (track == nullptr || false == editable(*track))
|
||||
{
|
||||
m_track_gizmo_using = false;
|
||||
return;
|
||||
}
|
||||
bool const pointmode = ui()->mode() == nodebank_panel::TRACK && m_track_point.valid() && m_track_point.path < static_cast<int>(track->m_paths.size());
|
||||
bool const controlmode = pointmode && false == editor_track::is_end(m_track_point.kind);
|
||||
|
||||
gizmo_frame const frame;
|
||||
auto &drag{m_track_gizmo_drag};
|
||||
auto const operation{m_track_gizmo_using ? drag.operation : track_gizmo_operation(*track, pointmode)};
|
||||
|
||||
if (false == m_track_gizmo_using)
|
||||
m_track_gizmo = track_gizmo_frame(*track, pointmode, operation);
|
||||
|
||||
glm::mat4 delta(1.0f);
|
||||
auto gizmo{operation == gizmo_operation::scale ? ImGuizmo::SCALE_Z : ImGuizmo::TRANSLATE};
|
||||
if (operation == gizmo_operation::rotate)
|
||||
gizmo = ImGuizmo::ROTATE_X | ImGuizmo::ROTATE_Y | ImGuizmo::ROTATE_Z;
|
||||
ImGuizmo::AllowAxisFlip(operation != gizmo_operation::scale);
|
||||
frame.manipulate(gizmo, m_track_gizmo, operation == gizmo_operation::translate ? m_gizmo_snap : 0.0f, &delta, operation == gizmo_operation::translate ? ImGuizmo::WORLD : ImGuizmo::LOCAL);
|
||||
ImGuizmo::AllowAxisFlip(true);
|
||||
|
||||
if (false == ImGuizmo::IsUsing())
|
||||
{
|
||||
if (m_track_gizmo_using)
|
||||
track_gizmo_end(*track);
|
||||
return;
|
||||
}
|
||||
if (false == m_track_gizmo_using)
|
||||
{
|
||||
track_gizmo_begin(*track, pointmode, operation);
|
||||
if (operation == gizmo_operation::scale && false == mouse_along(drag.origin, drag.axis, drag.grabbed))
|
||||
drag.grabbed = 0.0;
|
||||
}
|
||||
if (operation == gizmo_operation::rotate)
|
||||
track_gizmo_rotated(*track);
|
||||
else if (operation == gizmo_operation::scale)
|
||||
track_gizmo_scaled(*track);
|
||||
else if (controlmode)
|
||||
track_gizmo_control(*track, frame.camera + glm::dvec3(m_track_gizmo[3]) - editor_track::point_position(*track, editor_track::end_of(m_track_point)));
|
||||
else if (pointmode)
|
||||
track_gizmo_moved_point(*track, frame.camera + glm::dvec3(m_track_gizmo[3]));
|
||||
else if (glm::dvec3 const offset{delta[3]}; glm::length(offset) > 1e-9)
|
||||
{
|
||||
editor_track::translate(*track, offset);
|
||||
editor_track::follow(*track, m_track_joints);
|
||||
m_track_dirty = true;
|
||||
}
|
||||
track_gizmo_switches(*track);
|
||||
commit_track_drag(false);
|
||||
}
|
||||
@@ -9,6 +9,7 @@ http://mozilla.org/MPL/2.0/.
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "application/editormode.h"
|
||||
#include "application/editormodeshared.h"
|
||||
#include "application/editorprojection.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "editor/editorFormat.hpp"
|
||||
@@ -44,7 +45,6 @@ using geometry::signed_angle;
|
||||
|
||||
double constexpr kRimTolerance{0.05};
|
||||
double constexpr kMinimumRadius{80.0};
|
||||
float constexpr kDragPixels{5.0f};
|
||||
|
||||
glm::dvec2 heading_vector(double const Heading)
|
||||
{
|
||||
|
||||
@@ -745,4 +745,15 @@ void restore(std::vector<record> Records)
|
||||
settle();
|
||||
}
|
||||
|
||||
scene::layer_handle layer_of(TTrack const &Track)
|
||||
{
|
||||
auto const usable = [](scene::layer_handle const Layer) { return scene::Layers.valid(Layer) && scene::Layers.accepts(Layer) && false == scene::Layers.layer(Layer).name.ends_with(".txtf"); };
|
||||
auto layer{scene::Layers.resolve(Track.layer())};
|
||||
if (false == usable(layer))
|
||||
layer = scene::Layers.active();
|
||||
if (false == usable(layer))
|
||||
layer = 1;
|
||||
return layer;
|
||||
}
|
||||
|
||||
} // namespace alignment_store
|
||||
|
||||
@@ -101,5 +101,8 @@ std::optional<std::vector<record>> take_changes();
|
||||
void settle();
|
||||
// puts the records back as they were, for undo and redo
|
||||
void restore(std::vector<record> Records);
|
||||
// layer the editor keeps the records of the track in: its own, unless it's a terrain file (.txtf), whose binary version
|
||||
// (.btf) the editor doesn't read the records from
|
||||
scene::layer_handle layer_of(TTrack const &Track);
|
||||
|
||||
} // namespace alignment_store
|
||||
|
||||
@@ -159,4 +159,72 @@ inline double chord_length(segment_data const &Path)
|
||||
return glm::length(plan_of(Path.points[segment_data::point::end] - Path.points[segment_data::point::start]));
|
||||
}
|
||||
|
||||
inline bool has_controls(segment_data const &Path)
|
||||
{
|
||||
return Path.points[segment_data::point::control1] != glm::dvec3{} || Path.points[segment_data::point::control2] != glm::dvec3{};
|
||||
}
|
||||
|
||||
inline segment_data reversed(segment_data const &Path)
|
||||
{
|
||||
segment_data result{Path};
|
||||
result.points[segment_data::point::start] = Path.points[segment_data::point::end];
|
||||
result.points[segment_data::point::end] = Path.points[segment_data::point::start];
|
||||
result.points[segment_data::point::control1] = Path.points[segment_data::point::control2];
|
||||
result.points[segment_data::point::control2] = Path.points[segment_data::point::control1];
|
||||
result.rolls = {-Path.rolls[1], -Path.rolls[0]};
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::vector<segment_data> straight_piece(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, double const Length)
|
||||
{
|
||||
if (Length < 0.5)
|
||||
return {};
|
||||
segment_data path;
|
||||
path.points[segment_data::point::start] = Point;
|
||||
path.points[segment_data::point::end] = {Point.x + Direction.x * Length, Point.y + Grade * Length, Point.z + Direction.y * Length};
|
||||
return {path};
|
||||
}
|
||||
|
||||
inline segment_data part_of(segment_data const &Piece, double const From, double const To)
|
||||
{
|
||||
bezier const curve{Piece};
|
||||
segment_data result{Piece};
|
||||
result.points[segment_data::point::start] = curve.point(From);
|
||||
result.points[segment_data::point::end] = curve.point(To);
|
||||
if (has_controls(Piece))
|
||||
{
|
||||
result.points[segment_data::point::control1] = curve.first(From) * ((To - From) / 3.0);
|
||||
result.points[segment_data::point::control2] = -curve.first(To) * ((To - From) / 3.0);
|
||||
}
|
||||
result.rolls = {glm::mix(Piece.rolls[0], Piece.rolls[1], static_cast<float>(From)), glm::mix(Piece.rolls[0], Piece.rolls[1], static_cast<float>(To))};
|
||||
return result;
|
||||
}
|
||||
|
||||
inline std::vector<segment_data> arc_through(glm::dvec3 const &Point, glm::dvec2 const &Direction, double const Grade, glm::dvec2 const &Offset)
|
||||
{
|
||||
auto const along{glm::dot(Offset, Direction)};
|
||||
auto const lateral{cross(Direction, Offset)};
|
||||
auto const chord{glm::length(Offset)};
|
||||
if (along <= 0.0 || chord < 0.5)
|
||||
return {};
|
||||
if (std::abs(lateral) < 0.005 * chord + 0.05)
|
||||
return straight_piece(Point, Direction, Grade, along);
|
||||
auto const angle{2.0 * std::atan2(std::abs(lateral), along)};
|
||||
return arc_pieces(Point, Direction, Grade, chord / (2.0 * std::sin(angle * 0.5)), angle, lateral > 0.0 ? 1 : -1);
|
||||
}
|
||||
|
||||
inline double hermite(double const Length, double const Height0, double const Grade0, double const Height1, double const Grade1, double const T, double &Grade)
|
||||
{
|
||||
auto const t2{T * T};
|
||||
auto const t3{t2 * T};
|
||||
Grade = (6.0 * t2 - 6.0 * T) * (Height0 - Height1) / Length + (3.0 * t2 - 4.0 * T + 1.0) * Grade0 + (3.0 * t2 - 2.0 * T) * Grade1;
|
||||
return (2.0 * t3 - 3.0 * t2 + 1.0) * Height0 + (t3 - 2.0 * t2 + T) * Length * Grade0 + (3.0 * t2 - 2.0 * t3) * Height1 + (t3 - t2) * Length * Grade1;
|
||||
}
|
||||
|
||||
inline glm::dvec3 level(glm::dvec3 const &Vector)
|
||||
{
|
||||
glm::dvec3 const flat{Vector.x, 0.0, Vector.z};
|
||||
return glm::length(flat) > 1e-9 ? glm::normalize(flat) : glm::dvec3{0.0, 0.0, 1.0};
|
||||
}
|
||||
|
||||
} // namespace geometry
|
||||
|
||||
201
editor/editorGroundFollow.cpp
Normal file
201
editor/editorGroundFollow.cpp
Normal file
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorGroundFollow.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <iterator>
|
||||
|
||||
namespace ground_follow
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using geometry::bezier;
|
||||
using geometry::has_controls;
|
||||
using geometry::part_of;
|
||||
using geometry::plan_of;
|
||||
|
||||
double constexpr kStep{2.0};
|
||||
double constexpr kTolerance{0.1};
|
||||
double constexpr kSpacing{10.0};
|
||||
|
||||
struct samples
|
||||
{
|
||||
std::vector<double> chainages;
|
||||
std::vector<glm::dvec3> points;
|
||||
std::vector<glm::dvec2> directions;
|
||||
};
|
||||
|
||||
samples sample(std::vector<segment_data> const &Pieces, std::vector<double> const &Lengths, double const Total, ends const &Ends)
|
||||
{
|
||||
samples result;
|
||||
double reached{0.0};
|
||||
for (std::size_t i = 0; i < Pieces.size(); ++i)
|
||||
{
|
||||
bezier const curve{Pieces[i]};
|
||||
auto const count{std::max(1, static_cast<int>(std::ceil(Lengths[i] / kStep)))};
|
||||
for (int k = (i == 0 ? 1 : 0); k < count; ++k)
|
||||
{
|
||||
auto const along{Lengths[i] * k / count};
|
||||
auto const t{curve.parameter(along)};
|
||||
auto const chainage{reached + along};
|
||||
auto point{curve.point(t)};
|
||||
point.y = Ends.height0 + (Ends.height1 - Ends.height0) * chainage / Total + 50.0;
|
||||
result.chainages.push_back(chainage);
|
||||
result.points.push_back(point);
|
||||
result.directions.push_back(plan_of(curve.first(t)));
|
||||
}
|
||||
reached += Lengths[i];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void fit(knot_line &Line, std::vector<double> const &Chainages, std::vector<double> const &Targets)
|
||||
{
|
||||
for (std::size_t pass = 0; pass < Chainages.size(); ++pass)
|
||||
{
|
||||
auto worst{kTolerance};
|
||||
auto pick{Chainages.size()};
|
||||
for (std::size_t j = 0; j < Chainages.size(); ++j)
|
||||
{
|
||||
if (std::isnan(Targets[j]))
|
||||
continue;
|
||||
double grade{0.0};
|
||||
auto const miss{std::abs(Line.at(Chainages[j], grade) - Targets[j])};
|
||||
if (miss <= worst || false == Line.room(Chainages[j], kSpacing))
|
||||
continue;
|
||||
worst = miss;
|
||||
pick = j;
|
||||
}
|
||||
if (pick == Chainages.size())
|
||||
return;
|
||||
Line.insert(Chainages[pick], Targets[pick]);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<segment_data> cut(std::vector<segment_data> const &Pieces, std::vector<double> const &Lengths, std::vector<double> const &Knots, std::vector<std::pair<double, double>> &Spans)
|
||||
{
|
||||
std::vector<segment_data> result;
|
||||
double from{0.0};
|
||||
for (std::size_t i = 0; i < Pieces.size(); ++i)
|
||||
{
|
||||
bezier const curve{Pieces[i]};
|
||||
auto const to{from + Lengths[i]};
|
||||
double cut{from};
|
||||
double parameter{0.0};
|
||||
for (auto const knot : Knots)
|
||||
{
|
||||
if (knot < cut + 0.5 || knot > to - 0.5)
|
||||
continue;
|
||||
auto const next{curve.parameter(knot - from)};
|
||||
result.push_back(part_of(Pieces[i], parameter, next));
|
||||
Spans.emplace_back(cut, knot);
|
||||
cut = knot;
|
||||
parameter = next;
|
||||
}
|
||||
result.push_back(parameter > 0.0 ? part_of(Pieces[i], parameter, 1.0) : Pieces[i]);
|
||||
Spans.emplace_back(cut, to);
|
||||
from = to;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void lift(segment_data &Piece, std::pair<double, double> const &Span, knot_line const &Line)
|
||||
{
|
||||
auto &points{Piece.points};
|
||||
auto const [a, b]{Span};
|
||||
auto const length{b - a};
|
||||
double begin{0.0};
|
||||
double end{0.0};
|
||||
auto &start{points[segment_data::point::start]};
|
||||
auto &finish{points[segment_data::point::end]};
|
||||
auto &control1{points[segment_data::point::control1]};
|
||||
auto &control2{points[segment_data::point::control2]};
|
||||
start.y = Line.at(a, begin);
|
||||
finish.y = Line.at(b, end);
|
||||
if (has_controls(Piece))
|
||||
{
|
||||
control1.y = begin * glm::length(plan_of(control1));
|
||||
control2.y = -end * glm::length(plan_of(control2));
|
||||
return;
|
||||
}
|
||||
auto const linear{(finish.y - start.y) / std::max(length, 1e-6)};
|
||||
if (length < 1e-3 || (std::abs(begin - linear) < 1e-6 && std::abs(end - linear) < 1e-6))
|
||||
return;
|
||||
auto const along{glm::normalize(plan_of(finish - start)) * (length / 3.0)};
|
||||
control1 = {along.x, begin * length / 3.0, along.y};
|
||||
control2 = {-along.x, -end * length / 3.0, -along.y};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void knot_line::refresh()
|
||||
{
|
||||
auto const last{knots.size() - 1};
|
||||
slopes.assign(knots.size(), 0.0);
|
||||
for (std::size_t i = 0; i <= last; ++i)
|
||||
{
|
||||
auto const a{i == 0 ? 0 : i - 1};
|
||||
auto const b{i == last ? last : i + 1};
|
||||
slopes[i] = (heights[b] - heights[a]) / (knots[b] - knots[a]);
|
||||
}
|
||||
if (false == std::isnan(grade0))
|
||||
slopes.front() = grade0;
|
||||
if (false == std::isnan(grade1))
|
||||
slopes.back() = grade1;
|
||||
}
|
||||
|
||||
void knot_line::insert(double const Chainage, double const Height)
|
||||
{
|
||||
auto const where{std::upper_bound(knots.begin(), knots.end(), Chainage) - knots.begin()};
|
||||
knots.insert(knots.begin() + where, Chainage);
|
||||
heights.insert(heights.begin() + where, Height);
|
||||
refresh();
|
||||
}
|
||||
|
||||
double knot_line::at(double const Chainage, double &Grade) const
|
||||
{
|
||||
auto const next{static_cast<std::size_t>(std::upper_bound(knots.begin(), knots.end(), Chainage) - knots.begin())};
|
||||
auto const i{std::clamp<std::size_t>(next, 1, knots.size() - 1) - 1};
|
||||
auto const length{knots[i + 1] - knots[i]};
|
||||
return geometry::hermite(length, heights[i], slopes[i], heights[i + 1], slopes[i + 1], std::clamp((Chainage - knots[i]) / length, 0.0, 1.0), Grade);
|
||||
}
|
||||
|
||||
bool knot_line::room(double const Chainage, double const Spacing) const
|
||||
{
|
||||
auto const next{std::upper_bound(knots.begin(), knots.end(), Chainage)};
|
||||
return next != knots.end() && *next - Chainage >= Spacing && Chainage - *std::prev(next) >= Spacing;
|
||||
}
|
||||
|
||||
void follow(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed)
|
||||
{
|
||||
std::vector<double> lengths;
|
||||
double total{0.0};
|
||||
for (auto const &piece : Pieces)
|
||||
total += lengths.emplace_back(bezier{piece}.plan_length(256));
|
||||
if (total < 1e-3)
|
||||
return;
|
||||
auto const points{sample(Pieces, lengths, total, Ends)};
|
||||
auto const targets{Bed(points.points, points.directions)};
|
||||
knot_line line{{0.0, total}, {Ends.height0, Ends.height1}, {}, Ends.grade0, Ends.grade1};
|
||||
line.refresh();
|
||||
fit(line, points.chainages, targets);
|
||||
std::vector<std::pair<double, double>> spans;
|
||||
auto result{cut(Pieces, lengths, line.knots, spans)};
|
||||
for (std::size_t i = 0; i < result.size(); ++i)
|
||||
lift(result[i], spans[i], line);
|
||||
Pieces = std::move(result);
|
||||
}
|
||||
|
||||
} // namespace ground_follow
|
||||
47
editor/editorGroundFollow.hpp
Normal file
47
editor/editorGroundFollow.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "world/Segment.h"
|
||||
|
||||
namespace ground_follow
|
||||
{
|
||||
|
||||
using bed_query = std::function<std::vector<double>(std::vector<glm::dvec3> const &, std::vector<glm::dvec2> const &)>;
|
||||
|
||||
struct ends
|
||||
{
|
||||
double height0{0.0};
|
||||
double grade0{0.0};
|
||||
double height1{0.0};
|
||||
double grade1{0.0};
|
||||
};
|
||||
|
||||
struct knot_line
|
||||
{
|
||||
std::vector<double> knots;
|
||||
std::vector<double> heights;
|
||||
std::vector<double> slopes;
|
||||
double grade0{0.0};
|
||||
double grade1{0.0};
|
||||
|
||||
void refresh();
|
||||
void insert(double const Chainage, double const Height);
|
||||
double at(double const Chainage, double &Grade) const;
|
||||
bool room(double const Chainage, double const Spacing) const;
|
||||
};
|
||||
|
||||
void follow(std::vector<segment_data> &Pieces, ends const &Ends, bed_query const &Bed);
|
||||
|
||||
} // namespace ground_follow
|
||||
@@ -363,4 +363,29 @@ void restore(std::vector<record> Records)
|
||||
settle();
|
||||
}
|
||||
|
||||
void place(record &Entry, TTrack const &First)
|
||||
{
|
||||
auto const &main{First.m_paths.front()};
|
||||
geometry::bezier const curve{main};
|
||||
Entry.start = main.points[segment_data::point::start];
|
||||
auto const direction{geometry::plan_of(curve.first(0.0))};
|
||||
Entry.direction = glm::length(direction) > 1e-12 ? glm::normalize(direction) : glm::dvec2{0.0, 1.0};
|
||||
Entry.km = alignment_store::chainage_at(Entry.start);
|
||||
}
|
||||
|
||||
void classify(record &Entry, TTrack const &Switch)
|
||||
{
|
||||
geometry::bezier const main{Switch.m_paths[0]};
|
||||
auto const &diverging{Switch.m_paths[1]};
|
||||
Entry.hand = geometry::cross(Entry.direction, geometry::plan_of(diverging.points[segment_data::point::end] - Entry.start)) > 0.0 ? 'R' : 'L';
|
||||
auto const first{geometry::plan_of(main.first(0.0))};
|
||||
auto const last{geometry::plan_of(main.first(1.0))};
|
||||
auto const turn{glm::length(first) > 1e-12 && glm::length(last) > 1e-12 ? geometry::signed_angle(glm::normalize(first), glm::normalize(last)) : 0.0};
|
||||
Entry.main_radius = std::abs(turn) > 1e-6 ? main.plan_length() / turn : 0.0;
|
||||
if (std::abs(Entry.main_radius) > 20000.0)
|
||||
Entry.main_radius = 0.0;
|
||||
if (Entry.type == "Rz" || Entry.type == "Rlj" || Entry.type == "Rld")
|
||||
Entry.type = Entry.main_radius == 0.0 ? "Rz" : (Entry.main_radius > 0.0) == (Entry.hand == 'R') ? "Rlj" : "Rld";
|
||||
}
|
||||
|
||||
} // namespace switch_store
|
||||
|
||||
@@ -60,6 +60,8 @@ std::vector<std::string> follow();
|
||||
double branch_radius(record const &Record);
|
||||
// keeps the record in the file of the layer, in place of the one with the same name
|
||||
void put(record Entry, scene::layer_handle const Layer);
|
||||
void place(record &Entry, TTrack const &First);
|
||||
void classify(record &Entry, TTrack const &Switch);
|
||||
void forget(std::string const &Name);
|
||||
// on save: the records follow the renamed switches, the ones whose paths are gone are dropped
|
||||
void tidy();
|
||||
|
||||
@@ -44,6 +44,7 @@ using geometry::bezier;
|
||||
using geometry::chord_length;
|
||||
using geometry::cross;
|
||||
using geometry::plan_of;
|
||||
using geometry::reversed;
|
||||
using geometry::signed_angle;
|
||||
using geometry::turned;
|
||||
|
||||
@@ -94,17 +95,6 @@ double path_radius(segment_data const &Path, double const T)
|
||||
return curvature > 1e-7 ? 1.0 / curvature : 0.0;
|
||||
}
|
||||
|
||||
segment_data reversed(segment_data const &Path)
|
||||
{
|
||||
segment_data result{Path};
|
||||
result.points[segment_data::point::start] = Path.points[segment_data::point::end];
|
||||
result.points[segment_data::point::end] = Path.points[segment_data::point::start];
|
||||
result.points[segment_data::point::control1] = Path.points[segment_data::point::control2];
|
||||
result.points[segment_data::point::control2] = Path.points[segment_data::point::control1];
|
||||
result.rolls = {-Path.rolls[1], -Path.rolls[0]};
|
||||
return result;
|
||||
}
|
||||
|
||||
bool adjoining_direction(TTrack const *Neighbour, glm::dvec3 const &Joint, bool const Leaving, glm::dvec3 &Direction, double &Radius)
|
||||
{
|
||||
if (Neighbour == nullptr)
|
||||
|
||||
@@ -126,6 +126,60 @@ class editor_track
|
||||
static std::vector<std::pair<TTrack *, point_ref>> partners(TTrack &Track, point_ref const &End);
|
||||
static void align_controls(TTrack const &Track, point_ref const &Control, std::vector<std::pair<TTrack *, point_ref>> const &Partners);
|
||||
static point_ref end_of(point_ref const &Control);
|
||||
static glm::dvec3 control_vector(TTrack const &Track, point_ref const &Control);
|
||||
static glm::dvec3 outward(TTrack const &Track, point_ref const &Point);
|
||||
static point_ref switch_main(TTrack const &Track, point_ref Point);
|
||||
static std::size_t driven_paths(TTrack const &Track);
|
||||
static double joint_roll(TTrack const &Track, point_ref const &Point);
|
||||
static void roll_joined(TTrack &Other, point_ref const &Theirs, float const Roll);
|
||||
static void match_rolls(TTrack const &Track, std::vector<joint> const &Joints);
|
||||
|
||||
struct path_end
|
||||
{
|
||||
int path{0};
|
||||
bool atend{true};
|
||||
};
|
||||
static std::optional<path_end> stretch_end(TTrack const &Track, point_ref const &Point);
|
||||
static std::vector<segment_data> stretched(segment_data const &Path, bool const Atend, double const Length);
|
||||
static std::pair<glm::dvec3, glm::dvec3> end_axis(segment_data const &Path, bool const Atend);
|
||||
static point_ref stretch_remap(point_ref Point, int const Path, std::size_t const Pieces);
|
||||
|
||||
struct middle_point
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
glm::dvec3 tangent{0.0, 0.0, 1.0};
|
||||
double chainage{0.0};
|
||||
};
|
||||
static middle_point middle(TTrack const &Track);
|
||||
|
||||
struct edit
|
||||
{
|
||||
std::vector<std::pair<TTrack *, state>> states;
|
||||
std::vector<TTrack *> created;
|
||||
std::vector<TTrack *> removed;
|
||||
std::vector<TTrack *> changed;
|
||||
void capture(TTrack &Track);
|
||||
};
|
||||
|
||||
struct drive_spot
|
||||
{
|
||||
glm::dvec3 location{0.0};
|
||||
double yaw{0.0};
|
||||
glm::dvec2 tilt{0.0};
|
||||
bool rightswitch{false};
|
||||
};
|
||||
static std::optional<drive_spot> drive_spot_of(TTrack const &Switch);
|
||||
|
||||
struct straight_axis
|
||||
{
|
||||
glm::dvec3 origin{0.0};
|
||||
glm::dvec2 direction{0.0, 1.0};
|
||||
double length{0.0};
|
||||
double along(glm::dvec3 const &Point) const;
|
||||
double lateral(glm::dvec3 const &Point) const;
|
||||
};
|
||||
static std::optional<straight_axis> main_axis(TTrack const &Switch);
|
||||
static std::vector<segment_data> slid(TTrack const &Switch, double const Distance);
|
||||
|
||||
struct snap_target
|
||||
{
|
||||
|
||||
286
editor/editorTrackShape.cpp
Normal file
286
editor/editorTrackShape.cpp
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
This Source Code Form is subject to the
|
||||
terms of the Mozilla Public License, v.
|
||||
2.0. If a copy of the MPL was not
|
||||
distributed with this file, You can
|
||||
obtain one at
|
||||
http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
#include "stdafx.h"
|
||||
#include "editor/editorTrack.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
|
||||
#include "world/Track.h"
|
||||
#include "utilities/Globals.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
using geometry::arc_pieces;
|
||||
using geometry::bezier;
|
||||
using geometry::cross;
|
||||
using geometry::grade_of;
|
||||
using geometry::plan_distance;
|
||||
using geometry::plan_of;
|
||||
using geometry::reversed;
|
||||
using geometry::signed_angle;
|
||||
using geometry::straight_piece;
|
||||
|
||||
std::vector<segment_data> scaled(segment_data const &Path, bool const Atend, double const Length, double const Original)
|
||||
{
|
||||
auto const &start{Path.points[segment_data::point::start]};
|
||||
auto const &end{Path.points[segment_data::point::end]};
|
||||
auto const scale{Length / std::max(Original, 1e-6)};
|
||||
segment_data piece{Path};
|
||||
piece.points[segment_data::point::end] = start + (end - start) * scale;
|
||||
piece.points[segment_data::point::control1] *= scale;
|
||||
piece.points[segment_data::point::control2] *= scale;
|
||||
piece.radius *= static_cast<float>(scale);
|
||||
return {Atend ? piece : reversed(piece)};
|
||||
}
|
||||
|
||||
void spread_rolls(std::vector<segment_data> &Pieces, segment_data const &Path, double const Original, float const Radius)
|
||||
{
|
||||
double chainage{0.0};
|
||||
for (auto &piece : Pieces)
|
||||
{
|
||||
auto const piecelength{bezier{piece}.plan_length()};
|
||||
auto const from{static_cast<float>(std::clamp(chainage / Original, 0.0, 1.0))};
|
||||
auto const to{static_cast<float>(std::clamp((chainage + piecelength) / Original, 0.0, 1.0))};
|
||||
piece.rolls = {glm::mix(Path.rolls[0], Path.rolls[1], from), glm::mix(Path.rolls[0], Path.rolls[1], to)};
|
||||
piece.radius = Radius;
|
||||
chainage += piecelength;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::vector<segment_data> editor_track::stretched(segment_data const &Path, bool const Atend, double const Length)
|
||||
{
|
||||
auto const path{Atend ? Path : reversed(Path)};
|
||||
bezier const curve{path};
|
||||
auto const &start{path.points[segment_data::point::start]};
|
||||
auto const &end{path.points[segment_data::point::end]};
|
||||
auto const first{plan_of(curve.first(0.0))};
|
||||
auto const last{plan_of(curve.first(1.0))};
|
||||
if (glm::length(first) < 1e-9 || glm::length(last) < 1e-9)
|
||||
return {};
|
||||
glm::dvec2 const direction{glm::normalize(first)};
|
||||
auto const grade{grade_of(curve.first(0.0))};
|
||||
auto const original{curve.plan_length()};
|
||||
auto const turn{signed_angle(direction, glm::normalize(last))};
|
||||
auto const lateral{cross(direction, plan_of(end - start))};
|
||||
std::vector<segment_data> pieces;
|
||||
float radius{0.0f};
|
||||
if (std::abs(turn) < 1e-4 && std::abs(lateral) < 0.01)
|
||||
pieces = straight_piece(start, direction, grade, Length);
|
||||
else
|
||||
{
|
||||
auto const arcradius{original / std::abs(turn)};
|
||||
int const side{turn > 0.0 ? 1 : -1};
|
||||
auto const arc{arc_pieces(start, direction, grade, arcradius, std::abs(turn), side)};
|
||||
bool const circular{std::abs(turn) > 1e-4 && plan_distance(arc.back().points[segment_data::point::end], end) < 0.05 && plan_distance(arc.front().points[segment_data::point::start], start) < 1e-6};
|
||||
if (false == circular)
|
||||
return scaled(path, Atend, Length, original);
|
||||
pieces = arc_pieces(start, direction, grade, arcradius, std::min(Length / arcradius, glm::radians(330.0)), side);
|
||||
radius = path.radius;
|
||||
}
|
||||
spread_rolls(pieces, path, original, radius);
|
||||
if (false == Atend)
|
||||
{
|
||||
std::reverse(pieces.begin(), pieces.end());
|
||||
for (auto &piece : pieces)
|
||||
piece = reversed(piece);
|
||||
}
|
||||
return pieces;
|
||||
}
|
||||
|
||||
std::optional<editor_track::path_end> editor_track::stretch_end(TTrack const &Track, point_ref const &Point)
|
||||
{
|
||||
if (Track.eType != tt_Normal || false == is_supported(Track) || Track.m_paths.empty())
|
||||
return std::nullopt;
|
||||
auto const last{static_cast<int>(Track.m_paths.size()) - 1};
|
||||
if (false == Point.valid())
|
||||
{
|
||||
bool const startfree{false == is_connected(Track, {0, point_kind::start})};
|
||||
bool const endfree{false == is_connected(Track, {last, point_kind::end})};
|
||||
bool const atend{endfree || false == startfree};
|
||||
return path_end{atend ? last : 0, atend};
|
||||
}
|
||||
if (false == is_end(Point.kind) || Point.path > last)
|
||||
return std::nullopt;
|
||||
auto const position{point_position(Track, Point)};
|
||||
for (int i = 0; i <= last; ++i)
|
||||
if (i != Point.path && (plan_distance(Track.m_paths[i].points[segment_data::point::start], position) < geometry::same_point || plan_distance(Track.m_paths[i].points[segment_data::point::end], position) < geometry::same_point))
|
||||
return std::nullopt;
|
||||
return path_end{Point.path, Point.kind == point_kind::end};
|
||||
}
|
||||
|
||||
std::pair<glm::dvec3, glm::dvec3> editor_track::end_axis(segment_data const &Path, bool const Atend)
|
||||
{
|
||||
bezier const curve{Path};
|
||||
auto const tangent{Atend ? curve.first(1.0) : -curve.first(0.0)};
|
||||
return {Path.points[Atend ? segment_data::point::end : segment_data::point::start], glm::length(tangent) > 1e-9 ? glm::normalize(tangent) : glm::dvec3{}};
|
||||
}
|
||||
|
||||
editor_track::point_ref editor_track::stretch_remap(point_ref Point, int const Path, std::size_t const Pieces)
|
||||
{
|
||||
if (false == Point.valid() || Point.path < Path)
|
||||
return Point;
|
||||
auto const extra{static_cast<int>(Pieces) - 1};
|
||||
if (Point.path > Path || Point.kind == point_kind::end || Point.kind == point_kind::control2)
|
||||
Point.path += extra;
|
||||
return Point;
|
||||
}
|
||||
|
||||
std::size_t editor_track::driven_paths(TTrack const &Track)
|
||||
{
|
||||
return Track.eType == tt_Switch ? std::min<std::size_t>(1, Track.m_paths.size()) : Track.m_paths.size();
|
||||
}
|
||||
|
||||
editor_track::middle_point editor_track::middle(TTrack const &Track)
|
||||
{
|
||||
auto const count{driven_paths(Track)};
|
||||
double total{0.0};
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
total += bezier{Track.m_paths[i]}.plan_length();
|
||||
middle_point result{Track.location(), {0.0, 0.0, 1.0}, total * 0.5};
|
||||
double along{0.0};
|
||||
for (std::size_t i = 0; i < count; ++i)
|
||||
{
|
||||
bezier const curve{Track.m_paths[i]};
|
||||
auto const length{curve.plan_length()};
|
||||
if (along + length >= result.chainage || i + 1 == count)
|
||||
{
|
||||
auto const t{curve.parameter(result.chainage - along)};
|
||||
result.point = curve.point(t);
|
||||
result.tangent = curve.first(t);
|
||||
return result;
|
||||
}
|
||||
along += length;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
double editor_track::joint_roll(TTrack const &Track, point_ref const &Point)
|
||||
{
|
||||
if (Point.path < 0 || Point.path >= static_cast<int>(Track.m_paths.size()))
|
||||
return 0.0;
|
||||
auto const &path{Track.m_paths[Point.path]};
|
||||
return Point.kind == point_kind::start ? path.rolls[0] : -path.rolls[1];
|
||||
}
|
||||
|
||||
void editor_track::roll_joined(TTrack &Other, point_ref const &Theirs, float const Roll)
|
||||
{
|
||||
auto &theirs{Other.m_paths[Theirs.path]};
|
||||
if (Theirs.kind == point_kind::start)
|
||||
theirs.rolls[0] = -Roll;
|
||||
else
|
||||
theirs.rolls[1] = Roll;
|
||||
}
|
||||
|
||||
void editor_track::match_rolls(TTrack const &Track, std::vector<joint> const &Joints)
|
||||
{
|
||||
for (auto const &joint : Joints)
|
||||
if (joint.own.path < static_cast<int>(Track.m_paths.size()) && joint.theirs.path < static_cast<int>(joint.other->m_paths.size()))
|
||||
roll_joined(*joint.other, joint.theirs, static_cast<float>(joint_roll(Track, joint.own)));
|
||||
}
|
||||
|
||||
glm::dvec3 editor_track::outward(TTrack const &Track, point_ref const &Point)
|
||||
{
|
||||
bezier const curve{Track.m_paths[Point.path]};
|
||||
return Point.kind == point_kind::start ? -curve.first(0.0) : curve.first(1.0);
|
||||
}
|
||||
|
||||
editor_track::point_ref editor_track::switch_main(TTrack const &Track, point_ref Point)
|
||||
{
|
||||
if (Track.eType == tt_Switch && Point.valid() && Point.path > 0)
|
||||
Point.path = 0;
|
||||
return Point;
|
||||
}
|
||||
|
||||
glm::dvec3 editor_track::control_vector(TTrack const &Track, point_ref const &Control)
|
||||
{
|
||||
return point_position(Track, Control) - point_position(Track, end_of(Control));
|
||||
}
|
||||
|
||||
std::optional<editor_track::drive_spot> editor_track::drive_spot_of(TTrack const &Switch)
|
||||
{
|
||||
if (Switch.eType != tt_Switch || Switch.m_paths.size() < 2)
|
||||
return std::nullopt;
|
||||
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};
|
||||
auto heading{plan_of(along)};
|
||||
if (glm::length(heading) < 1e-9)
|
||||
return std::nullopt;
|
||||
heading = glm::normalize(heading);
|
||||
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;
|
||||
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<double>(main.rolls[0])};
|
||||
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)));
|
||||
return spot;
|
||||
}
|
||||
|
||||
double editor_track::straight_axis::along(glm::dvec3 const &Point) const
|
||||
{
|
||||
return glm::dot(plan_of(Point - origin), direction);
|
||||
}
|
||||
|
||||
double editor_track::straight_axis::lateral(glm::dvec3 const &Point) const
|
||||
{
|
||||
return std::abs(cross(direction, plan_of(Point - origin)));
|
||||
}
|
||||
|
||||
std::optional<editor_track::straight_axis> editor_track::main_axis(TTrack const &Switch)
|
||||
{
|
||||
if (Switch.m_paths.empty())
|
||||
return std::nullopt;
|
||||
auto const &main{Switch.m_paths.front()};
|
||||
auto const origin{main.points[segment_data::point::start]};
|
||||
auto const axis{plan_of(main.points[segment_data::point::end] - origin)};
|
||||
auto const length{glm::length(axis)};
|
||||
if (length < 1e-3)
|
||||
return std::nullopt;
|
||||
return straight_axis{origin, axis / length, length};
|
||||
}
|
||||
|
||||
std::vector<segment_data> editor_track::slid(TTrack const &Switch, double const Distance)
|
||||
{
|
||||
if (Switch.m_paths.empty())
|
||||
return {};
|
||||
auto const &main{Switch.m_paths.front()};
|
||||
auto const run{main.points[segment_data::point::end] - main.points[segment_data::point::start]};
|
||||
auto const planlength{glm::length(plan_of(run))};
|
||||
if (planlength < 1e-3)
|
||||
return {};
|
||||
auto const shift{run * (Distance / planlength)};
|
||||
auto paths{Switch.m_paths};
|
||||
for (auto &path : paths)
|
||||
{
|
||||
path.points[segment_data::point::start] += shift;
|
||||
path.points[segment_data::point::end] += shift;
|
||||
}
|
||||
return paths;
|
||||
}
|
||||
|
||||
void editor_track::edit::capture(TTrack &Track)
|
||||
{
|
||||
if (std::find(changed.begin(), changed.end(), &Track) != changed.end())
|
||||
return;
|
||||
states.emplace_back(&Track, editor_track::capture(Track));
|
||||
changed.push_back(&Track);
|
||||
}
|
||||
Reference in New Issue
Block a user