mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-23 03:01:10 +02:00
refactor(editor): the self-test commands in a file per group instead of one file of 4200 lines
editormodeselftest.cpp keeps the dispatch, the list of the commands and the step; the groups go to
editormodeselftest{scene,sleepers,sweep,track,switch,join,signal,terrain,rails}.cpp, the sleeper commands (beams,
sharedzones, sleepersbg, beamsnear, sleepercheck) in a group of their own; the frame count and one_line shared through
editormodeselftest.h. The output of geomhash, sleepercheck, wings, space, guards, crossings and switches is the same on the
seven sceneries of the regression.
This commit is contained in:
@@ -230,6 +230,15 @@ set(SOURCES
|
||||
"application/editormodesweep.cpp"
|
||||
"application/editormodeturntable.cpp"
|
||||
"application/editormodeselftest.cpp"
|
||||
"application/editormodeselftestscene.cpp"
|
||||
"application/editormodeselftestsleepers.cpp"
|
||||
"application/editormodeselftestsweep.cpp"
|
||||
"application/editormodeselftesttrack.cpp"
|
||||
"application/editormodeselftestswitch.cpp"
|
||||
"application/editormodeselftestjoin.cpp"
|
||||
"application/editormodeselftestsignal.cpp"
|
||||
"application/editormodeselftestterrain.cpp"
|
||||
"application/editormodeselftestrails.cpp"
|
||||
"application/scriptedinput.cpp"
|
||||
"input/editormouseinput.cpp"
|
||||
"application/editoruilayer.cpp"
|
||||
|
||||
@@ -2052,6 +2052,7 @@ class editor_mode : public application_mode, private editor_track::observer
|
||||
pause
|
||||
};
|
||||
selftest_result selftest_scene(std::string const &command, std::istringstream &words);
|
||||
selftest_result selftest_sleepers(std::string const &command, std::istringstream &words);
|
||||
selftest_result selftest_sweep(std::string const &command, std::istringstream &words);
|
||||
selftest_result selftest_track(std::string const &command, std::istringstream &words);
|
||||
selftest_result selftest_switch(std::string const &command, std::istringstream &words);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
33
application/editormodeselftest.h
Normal file
33
application/editormodeselftest.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
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 <algorithm>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
|
||||
// what the groups of the self-test commands share, each group in a file of its own
|
||||
namespace editor_selftest
|
||||
{
|
||||
|
||||
// frames gone through the selftest, for the measurements of perf
|
||||
inline std::size_t &frames()
|
||||
{
|
||||
static std::size_t count{0};
|
||||
return count;
|
||||
}
|
||||
|
||||
inline std::string one_line(std::string Text)
|
||||
{
|
||||
std::replace(Text.begin(), Text.end(), '\n', ';');
|
||||
return Text;
|
||||
}
|
||||
|
||||
} // namespace editor_selftest
|
||||
398
application/editormodeselftestjoin.cpp
Normal file
398
application/editormodeselftestjoin.cpp
Normal file
@@ -0,0 +1,398 @@
|
||||
/*
|
||||
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/editorSettings.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "application/application.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Event.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Sweep.h"
|
||||
#include "input/keyboardinput.h"
|
||||
#include "launcher/textures_scanner.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <algorithm>
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "scene/sceneterrain.h"
|
||||
#include "editor/editorSpeed.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include "application/editormodeselftest.h"
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_join(std::string const &command, std::istringstream &words)
|
||||
{
|
||||
if (command == "join" || command == "layto")
|
||||
{
|
||||
glm::dvec3 a{0.0};
|
||||
glm::dvec3 b{0.0};
|
||||
words >> a.x >> a.z >> b.x >> b.z;
|
||||
auto const from{editor_track::find_free_end(nullptr, 1, a, 3.0, {})};
|
||||
extend_tool tool;
|
||||
if (from.track == nullptr || false == free_end(*from.track, from.point.path, from.point.kind == editor_track::point_kind::end, tool))
|
||||
{
|
||||
WriteLog("SELFTEST " + command + ": no free end at the first point");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
std::vector<segment_data> pieces;
|
||||
std::string error;
|
||||
double length{0.0};
|
||||
if (command == "join")
|
||||
{
|
||||
double radius{0.0};
|
||||
int variant{0};
|
||||
int transitions{0};
|
||||
words >> radius >> variant >> transitions;
|
||||
auto const to{editor_track::find_free_end(nullptr, 1, b, 3.0, {from.track})};
|
||||
if (to.track == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST join: no free end at the second point");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
m_join_radius = radius;
|
||||
m_join_variant = variant;
|
||||
m_lay.transitions = transitions != 0;
|
||||
pieces = join_pieces(tool.track, from.point, tool.point, tool.direction, to, length, error);
|
||||
}
|
||||
else
|
||||
{
|
||||
int layout{0};
|
||||
words >> layout >> m_lay.radius;
|
||||
m_track_layout = static_cast<track_layout>(std::clamp(layout, 0, 3));
|
||||
m_extend = tool;
|
||||
m_extend.active = true;
|
||||
m_extend.mouse = {b.x, tool.point.y, b.z};
|
||||
m_extend.snap = {};
|
||||
pieces = extend_pieces();
|
||||
m_extend.active = false;
|
||||
}
|
||||
std::vector<TTrack *> created;
|
||||
for (auto const &piece : pieces)
|
||||
created.push_back(editor_track::create_path(*tool.track, piece));
|
||||
editor_track::commit(created);
|
||||
digitize_drawn(created);
|
||||
if (false == created.empty())
|
||||
push_track_snapshot({}, created);
|
||||
char const *const kinds[]{"none", "straight", "arc", "curve", "reverse curve", "free curve"};
|
||||
auto const &join{m_join_last};
|
||||
WriteLog(format("SELFTEST %s: %s, %zu paths, R %.1f m, cant %.0f mm, transitions %.1f m, V %.0f km/h, variants %d%s", command.c_str(), kinds[static_cast<int>(join.kind)], pieces.size(), join.radius, join.cant,
|
||||
join.transition, join.speed, join.variants, error.empty() ? "" : (", " + error).c_str()));
|
||||
auto heading{tool.direction};
|
||||
glm::dvec3 previous{tool.point};
|
||||
for (auto const *track : created)
|
||||
{
|
||||
auto const &path{track->m_paths.front()};
|
||||
geometry::bezier const curve{path};
|
||||
auto const begin{glm::normalize(geometry::plan_of(curve.first(0.0)))};
|
||||
auto const finish{glm::normalize(geometry::plan_of(curve.first(1.0)))};
|
||||
WriteLog(format("SELFTEST %s path %s: L %.2f m, R %.1f m, rolls %.2f %.2f, kink %.4f deg, step %.4f m", command.c_str(), track->name().c_str(), curve.plan_length(), static_cast<double>(path.radius),
|
||||
static_cast<double>(path.rolls[0]), static_cast<double>(path.rolls[1]), glm::degrees(std::abs(geometry::signed_angle(heading, begin))), glm::distance(previous, path.points[segment_data::point::start])));
|
||||
heading = finish;
|
||||
previous = path.points[segment_data::point::end];
|
||||
}
|
||||
if (false == created.empty())
|
||||
WriteLog(format("SELFTEST %s end: %.3f m from the target", command.c_str(), geometry::plan_distance(previous, b)));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "tracktab")
|
||||
{
|
||||
std::string key;
|
||||
words >> key;
|
||||
for (auto const &mode : track_modes())
|
||||
if (key == mode.key)
|
||||
show_track_tab(mode.tab);
|
||||
WriteLog(format("SELFTEST tracktab %s: %s", key.c_str(), track_mode_name().c_str()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "laychain")
|
||||
{
|
||||
int layout{0};
|
||||
words >> layout >> m_lay.radius;
|
||||
m_track_layout = static_cast<track_layout>(std::clamp(layout, 0, 3));
|
||||
m_lay.active = true;
|
||||
lay_cancel();
|
||||
auto const report = [&](std::string const &Step) {
|
||||
auto const *start{m_lay.start.track};
|
||||
WriteLog(format("SELFTEST laychain %s: chain %zu, points %zu, start %s%s at %.2f %.2f, history %zu, status %s", Step.c_str(), m_lay.chain.size(), m_lay.points.size(), start != nullptr ? start->name().c_str() : "-", start != nullptr && start->m_editorremoved ? " (removed)" : "",
|
||||
m_lay.points.empty() ? 0.0 : m_lay.points.front().x, m_lay.points.empty() ? 0.0 : m_lay.points.front().z, m_history.size(), m_lay.status.c_str()));
|
||||
};
|
||||
for (std::string word; words >> word;)
|
||||
{
|
||||
if (word == "b")
|
||||
lay_back();
|
||||
else if (word == "u")
|
||||
undo_last();
|
||||
else if (word == "r")
|
||||
redo_last();
|
||||
else
|
||||
{
|
||||
glm::dvec3 point{std::strtod(word.c_str(), nullptr), 5000.0, 0.0};
|
||||
words >> point.z;
|
||||
std::vector<char> found;
|
||||
auto const ground{ground_heights({point}, true, &found)};
|
||||
point.y = found[0] != 0 ? ground[0] : m_lay.points.empty() ? 0.0 : m_lay.points.back().y;
|
||||
m_lay.mouse_snap = editor_track::find_free_end(nullptr, 1, point, 3.0, {});
|
||||
m_lay.mouse = m_lay.mouse_snap.track != nullptr ? point : lay_bed(point);
|
||||
lay_click();
|
||||
word += format(" %.1f", point.z);
|
||||
}
|
||||
report(word);
|
||||
}
|
||||
lay_cancel();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "layclicks")
|
||||
{
|
||||
int layout{0};
|
||||
words >> layout >> m_lay.radius;
|
||||
m_track_layout = static_cast<track_layout>(std::clamp(layout, 0, 3));
|
||||
auto const active{m_lay.active};
|
||||
m_lay.active = true;
|
||||
lay_cancel();
|
||||
auto const before{m_history.size()};
|
||||
for (glm::dvec3 point{0.0}; words >> point.x >> point.z;)
|
||||
{
|
||||
point.y = 5000.0;
|
||||
std::vector<char> found;
|
||||
auto const ground{ground_heights({point}, true, &found)};
|
||||
point.y = found[0] != 0 ? ground[0] : m_lay.points.empty() ? 0.0 : m_lay.points.back().y;
|
||||
m_lay.mouse_snap = editor_track::find_free_end(nullptr, 1, point, 3.0, {});
|
||||
m_lay.mouse = m_lay.mouse_snap.track != nullptr ? point : lay_bed(point);
|
||||
WriteLog(format("SELFTEST layclicks ground %.3f, track %.3f", point.y, m_lay.mouse.y));
|
||||
lay_click();
|
||||
WriteLog(format("SELFTEST layclicks %.1f %.1f: %s", point.x, point.z, m_lay.status.c_str()));
|
||||
}
|
||||
lay_cancel();
|
||||
m_lay.active = active;
|
||||
glm::dvec2 heading{0.0};
|
||||
glm::dvec3 previous{0.0};
|
||||
double slope{0.0};
|
||||
bool first{true};
|
||||
double hanging{0.0};
|
||||
double buried{0.0};
|
||||
int paths{0};
|
||||
for (auto step{before}; step < m_history.size(); ++step)
|
||||
for (auto const *track : m_history[step].created)
|
||||
{
|
||||
auto const &path{track->m_paths.front()};
|
||||
geometry::bezier const curve{path};
|
||||
auto const begin{glm::normalize(geometry::plan_of(curve.first(0.0)))};
|
||||
auto const &a{path.points[segment_data::point::start]};
|
||||
auto const &b{path.points[segment_data::point::end]};
|
||||
auto const rise = [](glm::dvec3 const &Tangent) { return Tangent.y / std::max(1e-9, glm::length(geometry::plan_of(Tangent))); };
|
||||
WriteLog(format("SELFTEST layclicks path %s: %.2f %.2f -> %.2f %.2f, L %.2f m, R %.1f m, kink %.4f deg, step %.4f m, y %.3f -> %.3f, grade %.2f -> %.2f permille, grade break %.3f permille", track->name().c_str(), a.x, a.z, b.x, b.z, curve.plan_length(), static_cast<double>(path.radius),
|
||||
first ? 0.0 : glm::degrees(std::abs(geometry::signed_angle(heading, begin))), first ? 0.0 : glm::distance(previous, a), a.y, b.y, 1000.0 * rise(curve.first(0.0)), 1000.0 * rise(curve.first(1.0)),
|
||||
first ? 0.0 : 1000.0 * std::abs(rise(curve.first(0.0)) - slope)));
|
||||
slope = rise(curve.first(1.0));
|
||||
heading = glm::normalize(geometry::plan_of(curve.first(1.0)));
|
||||
previous = b;
|
||||
first = false;
|
||||
auto const count{std::max(1, static_cast<int>(std::ceil(curve.plan_length() / 2.0)))};
|
||||
for (int k = 0; k <= count; ++k)
|
||||
{
|
||||
auto const t{curve.parameter(curve.plan_length() * k / count)};
|
||||
auto const target{bed_heights({curve.point(t) + glm::dvec3{0.0, 50.0, 0.0}}, {geometry::plan_of(curve.first(t))}, track).front()};
|
||||
if (std::isnan(target))
|
||||
continue;
|
||||
hanging = std::max(hanging, curve.point(t).y - target);
|
||||
buried = std::max(buried, target - curve.point(t).y);
|
||||
}
|
||||
++paths;
|
||||
}
|
||||
WriteLog(format("SELFTEST layclicks %d paths, ballast foot over the ground up to %.3f m, under it up to %.3f m", paths, hanging, buried));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "joints")
|
||||
{
|
||||
std::vector<glm::dvec3> points;
|
||||
for (glm::dvec3 point{0.0}; words >> point.x >> point.z;)
|
||||
points.push_back(point);
|
||||
auto const scope{m_joints.scope};
|
||||
m_joints.scope = 1;
|
||||
joints_start();
|
||||
while (m_joints.next < m_joints.queue.size())
|
||||
joints_step();
|
||||
m_joints.scope = scope;
|
||||
char const *const kinds[]{"gap", "step", "kink", "cant jump", "grade break", "free end"};
|
||||
int around{0};
|
||||
for (auto const &issue : m_joints.issues)
|
||||
{
|
||||
if (false == points.empty() && issue.type == joint_issue::kind::open)
|
||||
continue;
|
||||
if (false == points.empty() && std::none_of(points.begin(), points.end(), [&](glm::dvec3 const &Point) { return geometry::plan_distance(Point, issue.position) < 5.0; }))
|
||||
continue;
|
||||
++around;
|
||||
WriteLog(format("SELFTEST joints: %s %.3f at %.1f %.1f, %s / %s", kinds[static_cast<int>(issue.type)], issue.value, issue.position.x, issue.position.z, issue.track != nullptr ? issue.track->name().c_str() : "-",
|
||||
issue.other != nullptr ? issue.other->name().c_str() : "-"));
|
||||
}
|
||||
WriteLog(format("SELFTEST joints: %d issues around the points, %zu in all", around, m_joints.issues.size()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "gizmo")
|
||||
{
|
||||
std::string action;
|
||||
words >> action;
|
||||
auto *track{selected_track()};
|
||||
if (track == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST gizmo: nothing selected");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (action == "set")
|
||||
{
|
||||
m_track_set.clear();
|
||||
for (std::string name; words >> name;)
|
||||
if (auto *each{simulation::Paths.find(name)}; each != nullptr)
|
||||
m_track_set.push_back(each);
|
||||
m_track_set_pivot = track_set_centre();
|
||||
WriteLog(format("SELFTEST gizmo set: %zu tracks, centre %.3f %.3f", m_track_set.size(), m_track_set_pivot.x, m_track_set_pivot.z));
|
||||
}
|
||||
else if (action == "setturn")
|
||||
{
|
||||
double degrees{0.0};
|
||||
words >> degrees;
|
||||
if (track_set_begin(true))
|
||||
{
|
||||
track_set_turn(glm::radians(degrees));
|
||||
track_set_end();
|
||||
}
|
||||
}
|
||||
else if (action == "op")
|
||||
{
|
||||
std::string operation;
|
||||
words >> operation;
|
||||
choose_work_area(work_area::tracks);
|
||||
m_gizmo_op = operation == "rotate" ? gizmo_operation::rotate : operation == "scale" ? gizmo_operation::scale : gizmo_operation::translate;
|
||||
int path{-1};
|
||||
std::string kind;
|
||||
words >> path >> kind;
|
||||
m_track_point = {};
|
||||
if (path >= 0)
|
||||
m_track_point = {path, kind == "c1" ? editor_track::point_kind::control1 : kind == "c2" ? editor_track::point_kind::control2 : kind == "end" ? editor_track::point_kind::end : editor_track::point_kind::start};
|
||||
}
|
||||
else if (action == "begin")
|
||||
{
|
||||
std::string operation;
|
||||
std::string kind;
|
||||
int path{-1};
|
||||
words >> operation >> path >> kind;
|
||||
m_track_point = {};
|
||||
if (path >= 0)
|
||||
m_track_point = {path, kind == "c1" ? editor_track::point_kind::control1 : kind == "c2" ? editor_track::point_kind::control2 : kind == "end" ? editor_track::point_kind::end : editor_track::point_kind::start};
|
||||
auto const wanted{operation == "rotate" ? gizmo_operation::rotate : operation == "scale" ? gizmo_operation::scale : gizmo_operation::translate};
|
||||
auto const previous{m_gizmo_op};
|
||||
m_gizmo_op = wanted;
|
||||
auto const chosen{track_gizmo_operation(*track, m_track_point.valid())};
|
||||
m_gizmo_op = previous;
|
||||
track_gizmo_begin(*track, m_track_point.valid(), chosen);
|
||||
WriteLog(format("SELFTEST gizmo begin %s: %s, %zu tracks dragged, %zu joints, %zu partners%s", operation.c_str(), chosen == gizmo_operation::rotate ? "rotate" : chosen == gizmo_operation::scale ? "scale" : "translate", m_track_drag.size(), m_track_joints.size(),
|
||||
m_track_gizmo_drag.partners.size(), m_track_gizmo_drag.fixed != glm::dvec3{} ? ", fixed direction" : ""));
|
||||
}
|
||||
else if (action == "turn")
|
||||
{
|
||||
double degrees{0.0};
|
||||
std::string axis{"y"};
|
||||
words >> degrees >> axis;
|
||||
m_track_gizmo_drag.ring = axis == "x" ? 0 : axis == "z" ? 2 : 1;
|
||||
track_gizmo_turn(*track, m_track_gizmo_drag.ring, glm::radians(degrees));
|
||||
track_gizmo_switches(*track);
|
||||
WriteLog(format("SELFTEST gizmo turn %s: %s", axis.c_str(), track_readout().c_str()));
|
||||
}
|
||||
else if (action == "length")
|
||||
{
|
||||
double length{0.0};
|
||||
words >> length;
|
||||
track_gizmo_length(*track, length);
|
||||
track_gizmo_switches(*track);
|
||||
WriteLog(format("SELFTEST gizmo length %.3f of %.3f, %zu pieces", m_track_gizmo_drag.stretch, m_track_gizmo_drag.length, m_track_gizmo_drag.pieces));
|
||||
}
|
||||
else if (action == "control")
|
||||
{
|
||||
glm::dvec3 control{0.0};
|
||||
words >> control.x >> control.y >> control.z;
|
||||
track_gizmo_control(*track, control);
|
||||
track_gizmo_switches(*track);
|
||||
}
|
||||
else if (action == "offset")
|
||||
{
|
||||
glm::dvec3 offset{0.0};
|
||||
words >> offset.x >> offset.y >> offset.z;
|
||||
if (m_track_point.valid())
|
||||
{
|
||||
track_gizmo_point(*track, editor_track::point_position(*track, m_track_point) + offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
editor_track::translate(*track, offset);
|
||||
editor_track::follow(*track, m_track_joints);
|
||||
}
|
||||
track_gizmo_switches(*track);
|
||||
m_track_dirty = true;
|
||||
}
|
||||
else if (action == "end")
|
||||
track_gizmo_end(*track);
|
||||
else if (action == "show")
|
||||
{
|
||||
std::vector<TTrack *> shown{track};
|
||||
for (auto *neighbour : editor_track::neighbours(*track))
|
||||
shown.push_back(neighbour);
|
||||
WriteLog(format("SELFTEST gizmo %s prev %s next %s", track->name().c_str(), track->trPrev != nullptr ? track->trPrev->name().c_str() : "-", track->trNext != nullptr ? track->trNext->name().c_str() : "-"));
|
||||
for (auto *each : shown)
|
||||
for (int i = 0; i < static_cast<int>(each->m_paths.size()); ++i)
|
||||
{
|
||||
auto const &path{each->m_paths[i]};
|
||||
auto const &a{path.points[segment_data::point::start]};
|
||||
auto const &b{path.points[segment_data::point::end]};
|
||||
auto const &c1{path.points[segment_data::point::control1]};
|
||||
auto const &c2{path.points[segment_data::point::control2]};
|
||||
WriteLog(format("SELFTEST gizmo path %s[%d] %.3f %.3f %.3f -> %.3f %.3f %.3f, c1 %.3f %.3f %.3f, c2 %.3f %.3f %.3f, L %.3f, R %.1f, rolls %.2f %.2f", each->name().c_str(), i, a.x, a.y, a.z, b.x, b.y, b.z, c1.x, c1.y, c1.z, c2.x, c2.y, c2.z,
|
||||
geometry::bezier{path}.plan_length(), path.radius, path.rolls[0], path.rolls[1]));
|
||||
}
|
||||
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})
|
||||
{
|
||||
geometry::bezier const own{track->m_paths[i]};
|
||||
bool const start{kind == editor_track::point_kind::start};
|
||||
auto const out{start ? -own.first(0.0) : own.first(1.0)};
|
||||
for (auto const &partner : editor_track::partners(*track, {i, kind}))
|
||||
{
|
||||
geometry::bezier const theirs{partner.first->m_paths[partner.second.path]};
|
||||
auto const in{partner.second.kind == editor_track::point_kind::start ? theirs.first(0.0) : -theirs.first(1.0)};
|
||||
auto const angle{glm::degrees(std::acos(std::clamp(glm::dot(glm::normalize(out), glm::normalize(in)), -1.0, 1.0)))};
|
||||
auto const gap{glm::distance(editor_track::point_position(*track, {i, kind}), editor_track::point_position(*partner.first, partner.second))};
|
||||
WriteLog(format("SELFTEST gizmo joint %s[%d].%s - %s[%d]: kink %.4f deg, gap %.4f m", track->name().c_str(), i, start ? "start" : "end", partner.first->name().c_str(), partner.second.path, angle, gap));
|
||||
}
|
||||
}
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
return selftest_result::unhandled;
|
||||
}
|
||||
1067
application/editormodeselftestrails.cpp
Normal file
1067
application/editormodeselftestrails.cpp
Normal file
File diff suppressed because it is too large
Load Diff
567
application/editormodeselftestscene.cpp
Normal file
567
application/editormodeselftestscene.cpp
Normal file
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
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/editorSettings.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "application/application.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Event.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Sweep.h"
|
||||
#include "input/keyboardinput.h"
|
||||
#include "launcher/textures_scanner.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <algorithm>
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "scene/sceneterrain.h"
|
||||
#include "editor/editorSpeed.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include "application/editormodeselftest.h"
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_scene(std::string const &command, std::istringstream &words)
|
||||
{
|
||||
auto &test{m_selftest};
|
||||
if (command == "camera")
|
||||
{
|
||||
glm::dvec3 from;
|
||||
glm::dvec3 to;
|
||||
words >> from.x >> from.y >> from.z >> to.x >> to.y >> to.z;
|
||||
auto const look{glm::normalize(to - from)};
|
||||
Camera.Pos = from;
|
||||
Camera.Angle = glm::vec3(static_cast<float>(std::asin(std::clamp(look.y, -1.0, 1.0))), static_cast<float>(std::atan2(-look.x, -look.z)), 0.0f);
|
||||
Global.pCamera = Camera;
|
||||
m_focus_active = false;
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "zoom")
|
||||
{
|
||||
float factor{1.0f};
|
||||
words >> factor;
|
||||
Global.ZoomFactor = std::max(factor, 0.01f);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "selectat")
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
words >> point.x >> point.z;
|
||||
TTrack *nearest{nullptr};
|
||||
double distance{1e9};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->m_paths.empty())
|
||||
continue;
|
||||
auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)};
|
||||
if (d < distance)
|
||||
{
|
||||
distance = d;
|
||||
nearest = track;
|
||||
}
|
||||
}
|
||||
m_node = distance < 3.0 ? nearest : nullptr;
|
||||
m_track_point = {};
|
||||
ui()->set_node(m_node);
|
||||
WriteLog(format("SELFTEST selectat %.1f %.1f: %s", point.x, point.z, m_node != nullptr ? m_node->name().c_str() : "nothing"));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "select")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
m_node = simulation::Paths.find(name);
|
||||
m_track_point = {};
|
||||
ui()->set_node(m_node);
|
||||
WriteLog("SELFTEST select " + name + (m_node != nullptr ? "" : ": no such track"));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "perf")
|
||||
{
|
||||
// the frames drawn and the time gone since the last perf, with the timings and the counts the renderer keeps
|
||||
std::string label;
|
||||
words >> label;
|
||||
static auto last{std::chrono::steady_clock::now()};
|
||||
static std::size_t lastframe{0};
|
||||
auto const now{std::chrono::steady_clock::now()};
|
||||
auto const frames{editor_selftest::frames() - lastframe};
|
||||
auto const elapsed{std::chrono::duration<double, std::milli>(now - last).count()};
|
||||
WriteLog(format("SELFTEST perf %s: %zu frames, %.3f ms per frame | %s | %s", label.c_str(), frames, frames > 0 ? elapsed / frames : 0.0, editor_selftest::one_line(GfxRenderer->info_times()).c_str(), editor_selftest::one_line(GfxRenderer->info_stats()).c_str()));
|
||||
last = now;
|
||||
lastframe = editor_selftest::frames();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "rebuildall")
|
||||
{
|
||||
// the geometry of every path made anew, as the editor does it after a change, timed
|
||||
auto const start{std::chrono::steady_clock::now()};
|
||||
auto count{0};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved)
|
||||
continue;
|
||||
auto §ion{simulation::Region->section(track->location())};
|
||||
if (false == section.m_geometrycreated)
|
||||
continue;
|
||||
track->rebuild_geometry(section.m_geometrybank);
|
||||
++count;
|
||||
}
|
||||
WriteLog(format("SELFTEST rebuildall: %d paths in %.1f ms", count, std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count()));
|
||||
// the sleepers laid again on all the tracks, which the renderer otherwise does as they come in sight, timed by section
|
||||
std::map<scene::basic_section const *, double> sectiontimes;
|
||||
auto laidcount{0};
|
||||
std::size_t parts{0};
|
||||
auto laytime{0.0};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || false == track->m_sleepers3d.active)
|
||||
continue;
|
||||
auto const laystart{std::chrono::steady_clock::now()};
|
||||
track_sleepers::lay(*track);
|
||||
auto const took{std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - laystart).count()};
|
||||
laytime += took;
|
||||
sectiontimes[&simulation::Region->section(track->location())] += took;
|
||||
++laidcount;
|
||||
for (auto const &instances : track->m_sleepers3d.instances)
|
||||
parts += instances.size();
|
||||
}
|
||||
auto slowest{0.0};
|
||||
for (auto const §iontime : sectiontimes)
|
||||
slowest = std::max(slowest, sectiontime.second);
|
||||
WriteLog(format("SELFTEST rebuildall sleepers3d: %d tracks laid in %.1f ms, %zu parts; %zu sections, %.2f ms per section, %.2f ms the slowest", laidcount, laytime, parts, sectiontimes.size(),
|
||||
(sectiontimes.empty() ? 0.0 : laytime / sectiontimes.size()), slowest));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "frames")
|
||||
{
|
||||
std::string name;
|
||||
double from{0.0};
|
||||
double to{0.0};
|
||||
double step{0.1};
|
||||
words >> name >> from >> to >> step;
|
||||
auto *sweep{simulation::Sweeps.find(name)};
|
||||
if (sweep == nullptr)
|
||||
return selftest_result::handled;
|
||||
auto const side{sweep->definition().lateral >= 0.0 ? 1 : 0};
|
||||
if (step <= 0.0)
|
||||
return selftest_result::handled;
|
||||
for (int i = 0; from + i * step <= to + 1e-9; ++i)
|
||||
{
|
||||
auto const station{from + i * step};
|
||||
auto const at{sweep->frame_at(station)};
|
||||
WriteLog(format("SELFTEST frame %s %.2f: %.3f %.3f %.3f widening %.4f cant %.4f setback %.4f", name.c_str(), station, at.position.x, at.position.y, at.position.z, at.widening[side], at.cant[side], sweep->setback(at)));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "weather")
|
||||
{
|
||||
auto const &time{simulation::Time.data()};
|
||||
WriteLog(format("SELFTEST weather: %02d:%02d, fog end %.0f m, overcast %.2f", time.wHour, time.wMinute, Global.fFogEnd, Global.Overcast));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "projtest")
|
||||
{
|
||||
screen_projection const projection;
|
||||
for (std::size_t i = 0; i < m_sweep.outline.size() && i < 45; i += 4)
|
||||
{
|
||||
ImVec2 screen;
|
||||
auto const shown{projection.project(m_sweep.outline[i], screen)};
|
||||
auto const clip{projection.clip(m_sweep.outline[i])};
|
||||
WriteLog(format("SELFTEST proj %zu: %.1f %.1f %.1f -> %d %.0f %.0f w %.2f", i, m_sweep.outline[i].x, m_sweep.outline[i].y, m_sweep.outline[i].z, shown ? 1 : 0, screen.x, screen.y, clip.w));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "newscenery")
|
||||
{
|
||||
WriteLog(restart_for_new_scenery() ? "SELFTEST newscenery: started" : "SELFTEST newscenery: failed");
|
||||
return selftest_result::pause;
|
||||
}
|
||||
if (command == "vehicles")
|
||||
{
|
||||
for (auto *vehicle : simulation::Vehicles.sequence())
|
||||
{
|
||||
if (vehicle == nullptr)
|
||||
continue;
|
||||
auto const head{vehicle->HeadPosition()};
|
||||
auto const front{vehicle->VectorFront()};
|
||||
auto const coupler = [&](int const End) {
|
||||
auto const &coupler{vehicle->MoverParameters->Couplers[End]};
|
||||
return coupler.Connected != nullptr ? coupler.Connected->Name + "/" + std::to_string(coupler.CouplingFlag) : std::string{"-"};
|
||||
};
|
||||
WriteLog(format("SELFTEST vehicle %s head %.2f %.2f front %.2f %.2f dir %d coupler0 %s coupler1 %s", vehicle->name().c_str(), head.x, head.z, front.x, front.z, vehicle->iDirection, coupler(0).c_str(), coupler(1).c_str()));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "mouseat")
|
||||
{
|
||||
glm::dvec3 at{0.0};
|
||||
words >> at.x >> at.y >> at.z;
|
||||
ImVec2 projected;
|
||||
if (false == screen_projection{}.project(at, projected))
|
||||
{
|
||||
WriteLog("SELFTEST mouseat: behind the camera");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
test.input.mouse_to(glm::dvec2{projected.x, projected.y});
|
||||
WriteLog(format("SELFTEST mouse %.0f %.0f", projected.x, projected.y));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "glide")
|
||||
{
|
||||
glm::dvec3 at{0.0};
|
||||
int steps{8};
|
||||
int every{2};
|
||||
std::string shots;
|
||||
words >> at.x >> at.y >> at.z >> steps >> every >> shots;
|
||||
ImVec2 projected;
|
||||
if (false == screen_projection{}.project(at, projected))
|
||||
return selftest_result::handled;
|
||||
glm::dvec2 const from{test.input.mouse().value_or(glm::dvec2{projected.x, projected.y})};
|
||||
glm::dvec2 const to{projected.x, projected.y};
|
||||
std::vector<std::string> expanded;
|
||||
for (int step = 1; step <= std::max(1, steps); ++step)
|
||||
{
|
||||
auto const t{static_cast<double>(step) / std::max(1, steps)};
|
||||
auto const eased{t * t * (3.0 - 2.0 * t)};
|
||||
auto const point{glm::mix(from, to, eased)};
|
||||
expanded.push_back(format("mouse %.1f %.1f", point.x, point.y));
|
||||
expanded.push_back("wait " + std::to_string(std::max(1, every)));
|
||||
if (shots == "shot")
|
||||
expanded.push_back("screenshot");
|
||||
}
|
||||
test.lines.insert(test.lines.begin() + static_cast<std::ptrdiff_t>(test.next), expanded.begin(), expanded.end());
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (test.input.dispatch(command, words))
|
||||
return selftest_result::handled;
|
||||
if (command == "screenshot")
|
||||
{
|
||||
Application.queue_screenshot();
|
||||
test.wait = 3;
|
||||
return selftest_result::pause;
|
||||
}
|
||||
if (command == "save")
|
||||
{
|
||||
save();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "ortho")
|
||||
{
|
||||
float extent{0.0f};
|
||||
words >> extent;
|
||||
choose_work_area(work_area::tracks);
|
||||
if (Global.EditorOrtho == extent < 0.0f)
|
||||
toggle_ortho();
|
||||
if (extent > 0.0f)
|
||||
Global.EditorOrthoExtent = std::clamp(extent, 5.0f, 50000.0f);
|
||||
WriteLog(format("SELFTEST ortho extent %.0f", Global.EditorOrthoExtent));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "mapstate")
|
||||
{
|
||||
auto const marked{scene::Layers.empty() ? std::vector<std::string>{} : scene::Layers.marked(scene::layer_handle{1}, "//$o")};
|
||||
WriteLog(format("SELFTEST mapstate orthophoto %d top %d extent %.0f mark %s camera %.1f %.1f %.1f", m_orthophoto.enabled() ? 1 : 0, Global.EditorOrtho ? 1 : 0, Global.EditorOrthoExtent, marked.empty() ? "-" : marked.front().c_str(), Camera.Pos.x, Camera.Pos.y, Camera.Pos.z));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "trainset")
|
||||
{
|
||||
std::string name;
|
||||
std::string vehicle;
|
||||
std::string skin;
|
||||
int facing{1};
|
||||
words >> name >> vehicle >> skin >> facing >> m_vehicle.velocity;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
if (track == nullptr)
|
||||
return selftest_result::handled;
|
||||
auto desc{std::make_shared<ui::vehicle_desc>()};
|
||||
desc->path = vehicle;
|
||||
auto set{std::make_shared<ui::skin_set>()};
|
||||
set->skin = skin;
|
||||
m_vehicle.consist.clear();
|
||||
m_vehicle.vehicle = desc;
|
||||
m_vehicle.skin = set;
|
||||
vehicle_start(*track, geometry::bezier{track->m_paths.front()}.point(0.5));
|
||||
m_vehicle.facing = facing;
|
||||
auto const placed{vehicle_place()};
|
||||
WriteLog(format("SELFTEST trainset %s placed %d pending %d: %s", name.c_str(), placed ? 1 : 0, static_cast<int>(m_vehicle.trainsets.size()), m_vehicle.status.c_str()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "selectmodel")
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
words >> point.x >> point.z;
|
||||
TAnimModel *nearest{nullptr};
|
||||
auto best{50.0};
|
||||
for (auto *model : simulation::Instances.sequence())
|
||||
{
|
||||
if (model == nullptr)
|
||||
continue;
|
||||
auto const distance{glm::length(glm::dvec2{model->location().x - point.x, model->location().z - point.z})};
|
||||
if (distance < best)
|
||||
{
|
||||
best = distance;
|
||||
nearest = model;
|
||||
}
|
||||
}
|
||||
m_node = nearest;
|
||||
select_include(0);
|
||||
ui()->set_node(m_node);
|
||||
WriteLog("SELFTEST selectmodel: " + (nearest != nullptr ? format("%s at %.2f %.2f %.2f", nearest->name().c_str(), nearest->location().x, nearest->location().y, nearest->location().z) : std::string{"none"}));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "addmodel")
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
words >> point.x >> point.z;
|
||||
TAnimModel *nearest{nullptr};
|
||||
auto best{50.0};
|
||||
for (auto *model : simulation::Instances.sequence())
|
||||
{
|
||||
if (model == nullptr)
|
||||
continue;
|
||||
auto const distance{glm::length(glm::dvec2{model->location().x - point.x, model->location().z - point.z})};
|
||||
if (distance < best)
|
||||
{
|
||||
best = distance;
|
||||
nearest = model;
|
||||
}
|
||||
}
|
||||
select_node(nearest, true);
|
||||
WriteLog("SELFTEST addmodel: " + (nearest != nullptr ? format("%s at %.2f %.2f %.2f", nearest->name().c_str(), nearest->location().x, nearest->location().y, nearest->location().z) : std::string{"none"}));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "selection")
|
||||
{
|
||||
std::string text;
|
||||
for (auto *node : selection_with_groups())
|
||||
text += format(" [%s %.2f %.2f %.2f group %d]", node->name().c_str(), node->location().x, node->location().y, node->location().z, static_cast<int>(node->group()));
|
||||
WriteLog("SELFTEST selection:" + text);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "moveselection" && m_node != nullptr)
|
||||
{
|
||||
glm::dvec3 offset{0.0};
|
||||
words >> offset.x >> offset.y >> offset.z;
|
||||
selection_snapshots(EditorSnapshot::Action::Move);
|
||||
auto const before{m_node->location()};
|
||||
m_editor.translate(m_node, before + offset, true);
|
||||
selection_follow(m_node->location() - before, glm::vec3(0.0f), m_node->location());
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "turnselection" && m_node != nullptr)
|
||||
{
|
||||
float degrees{0.0f};
|
||||
words >> degrees;
|
||||
selection_snapshots(EditorSnapshot::Action::Rotate);
|
||||
glm::vec3 const turned{0.0f, degrees, 0.0f};
|
||||
m_editor.rotate(m_node, turned, 0.0f);
|
||||
selection_follow(glm::dvec3(0.0), turned, m_node->location());
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "copy")
|
||||
{
|
||||
copy_selection();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "paste")
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
words >> point.x >> point.y >> point.z;
|
||||
paste_clipboard(point);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "group")
|
||||
{
|
||||
group_selection();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "ungroup")
|
||||
{
|
||||
ungroup_selection();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "drop")
|
||||
{
|
||||
drop_selection_to_ground();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "delete")
|
||||
{
|
||||
delete_selected();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "selectinclude")
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
words >> point.x >> point.z;
|
||||
scene::instance_handle nearest{0};
|
||||
auto best{50.0};
|
||||
m_node = nullptr;
|
||||
for (scene::instance_handle instance = 1; instance <= scene::Layers.instance_count(); ++instance)
|
||||
{
|
||||
if (false == scene::Layers.removable(instance))
|
||||
continue;
|
||||
select_include(instance);
|
||||
bend_source source;
|
||||
if (false == bend_source_of_selection(source))
|
||||
continue;
|
||||
auto const at{glm::dvec3(source.transform[3])};
|
||||
auto const distance{glm::length(glm::dvec2{at.x - point.x, at.z - point.z})};
|
||||
if (distance < best)
|
||||
{
|
||||
best = distance;
|
||||
nearest = instance;
|
||||
}
|
||||
}
|
||||
select_include(nearest);
|
||||
WriteLog("SELFTEST selectinclude: " + (nearest != 0 ? *scene::Layers.instance(nearest).file : std::string{"none"}));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "modelbox")
|
||||
{
|
||||
auto *model{dynamic_cast<TAnimModel *>(m_node)};
|
||||
bend_source source;
|
||||
if (model == nullptr || false == bend_source_of_selection(source))
|
||||
{
|
||||
WriteLog("SELFTEST modelbox: no model selected");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
scene::node_data data;
|
||||
sweep_node probe{data};
|
||||
sweep_node::state state;
|
||||
state.model = source.file;
|
||||
state.skin = source.skin;
|
||||
state.scale = source.scale;
|
||||
probe.define(state);
|
||||
glm::dvec3 boxlow{std::numeric_limits<double>::max()};
|
||||
glm::dvec3 boxhigh{-std::numeric_limits<double>::max()};
|
||||
for (auto const &item : probe.items())
|
||||
{
|
||||
auto const transform{source.transform * glm::scale(glm::dmat4(1.0), source.scale) * item.transform};
|
||||
std::vector<TSubModel *> pending{item.model->GetSMRoot()};
|
||||
while (false == pending.empty())
|
||||
{
|
||||
auto *submodel{pending.back()};
|
||||
pending.pop_back();
|
||||
for (; submodel != nullptr; submodel = submodel->Next)
|
||||
{
|
||||
if (submodel->Child != nullptr)
|
||||
pending.push_back(submodel->Child);
|
||||
if (submodel->eType != GL_TRIANGLES || submodel->fSquareMinDist > 0.f || (submodel->m_geometry.handle.bank == 0 && submodel->m_geometry.handle.chunk == 0))
|
||||
continue;
|
||||
glm::dmat4 local{1.0};
|
||||
for (auto *parent = submodel; parent != nullptr; parent = parent->Parent)
|
||||
if ((parent->iFlags & 0xC000) != 0 && parent->GetMatrix() != nullptr)
|
||||
local = glm::dmat4(glm::make_mat4(parent->GetMatrix()->readArray())) * local;
|
||||
for (auto const &vertex : GfxRenderer->Vertices(submodel->m_geometry.handle))
|
||||
{
|
||||
auto const world{glm::dvec3(transform * local * glm::dvec4(glm::dvec3(vertex.position), 1.0))};
|
||||
boxlow = glm::min(boxlow, world);
|
||||
boxhigh = glm::max(boxhigh, world);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
WriteLog(format("SELFTEST modelbox: %.2f %.2f %.2f - %.2f %.2f %.2f", boxlow.x, boxlow.y, boxlow.z, boxhigh.x, boxhigh.y, boxhigh.z));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "key")
|
||||
{
|
||||
std::string key;
|
||||
std::string action;
|
||||
std::string mods;
|
||||
words >> key >> action >> mods;
|
||||
if (key == "RMB")
|
||||
m_input.mouse.button(GLFW_MOUSE_BUTTON_RIGHT, action == "press" ? GLFW_PRESS : GLFW_RELEASE);
|
||||
else
|
||||
{
|
||||
auto const code{key == "S" ? GLFW_KEY_S : key == "W" ? GLFW_KEY_W : key == "CTRL" ? GLFW_KEY_LEFT_CONTROL : 0};
|
||||
on_key(code, 0, action == "press" ? GLFW_PRESS : GLFW_RELEASE, mods == "ctrl" ? GLFW_MOD_CONTROL : 0);
|
||||
}
|
||||
WriteLog(format("SELFTEST key %s %s %s: camera %.2f %.2f %.2f, key S %d, right button %d", key.c_str(), action.c_str(), mods.c_str(), Camera.Pos.x, Camera.Pos.y, Camera.Pos.z, static_cast<int>(input::keys[GLFW_KEY_S]), m_input.mouse.button(GLFW_MOUSE_BUTTON_RIGHT)));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "event")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto *event{simulation::Events.FindEvent(name)};
|
||||
if (event != nullptr)
|
||||
simulation::Events.AddToQuery(event, nullptr);
|
||||
WriteLog("SELFTEST event " + name + (event != nullptr ? " queued" : ": no such event"));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "memcell")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto *cell{simulation::Memory.find(name)};
|
||||
WriteLog("SELFTEST memcell " + name + (cell != nullptr ? format(": %s %.1f %.1f", cell->Text().c_str(), cell->Value1(), cell->Value2()) : std::string{": none"}));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "newscenerypopup")
|
||||
{
|
||||
m_newscenery_asked = true;
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "openscenerypopup")
|
||||
{
|
||||
m_openscenery_asked = true;
|
||||
std::snprintf(m_openscenery_search, sizeof(m_openscenery_search), "%s", "zz_");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "openscenery")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
WriteLog(restart_editor(name) ? "SELFTEST openscenery: started " + name : "SELFTEST openscenery: failed");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "undo")
|
||||
{
|
||||
undo_last();
|
||||
WriteLog("SELFTEST undo");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "redo")
|
||||
{
|
||||
redo_last();
|
||||
WriteLog("SELFTEST redo");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "log")
|
||||
{
|
||||
std::string text;
|
||||
std::getline(words, text);
|
||||
WriteLog("SELFTEST" + text);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
return selftest_result::unhandled;
|
||||
}
|
||||
201
application/editormodeselftestsignal.cpp
Normal file
201
application/editormodeselftestsignal.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 "application/editormode.h"
|
||||
#include "editor/editorSettings.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "application/application.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Event.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Sweep.h"
|
||||
#include "input/keyboardinput.h"
|
||||
#include "launcher/textures_scanner.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <algorithm>
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "scene/sceneterrain.h"
|
||||
#include "editor/editorSpeed.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include "application/editormodeselftest.h"
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_signal(std::string const &command, std::istringstream &words)
|
||||
{
|
||||
if (command == "signal" || command == "signalmove" || command == "signaldelete" || command == "signalrename" || command == "signalprobe")
|
||||
{
|
||||
std::map<std::string, std::string> values;
|
||||
std::string pair;
|
||||
while (words >> pair)
|
||||
{
|
||||
auto const equals{pair.find('=')};
|
||||
if (equals != std::string::npos)
|
||||
values[pair.substr(0, equals)] = pair.substr(equals + 1);
|
||||
}
|
||||
show_track_tab(track_tab::signals);
|
||||
signal_refresh(true);
|
||||
auto &tool{m_signal};
|
||||
auto const standing_named = [&](std::string const &Name) -> signal_standing const * {
|
||||
auto const found{std::find_if(tool.standing.begin(), tool.standing.end(), [&](signal_standing const &Standing) { return Standing.name == Name; })};
|
||||
return found != tool.standing.end() ? &*found : nullptr;
|
||||
};
|
||||
auto const spot_on = [&](signal_template const &Template, int const Facing, int const Side, signal_spot &Spot) {
|
||||
auto *track{simulation::Paths.find(values["track"])};
|
||||
if (track == nullptr)
|
||||
return false;
|
||||
auto const path{values.count("path") ? std::stoi(values["path"]) : 0};
|
||||
auto const at{values.count("at") ? std::stod(values["at"]) : 0.5};
|
||||
geometry::bezier const curve{track->m_paths[std::clamp(path, 0, static_cast<int>(track->m_paths.size()) - 1)]};
|
||||
return signal_spot_at(*track, path, curve.point(at), curve.point(at), Facing, Side, Template, Spot);
|
||||
};
|
||||
if (command == "signal")
|
||||
{
|
||||
auto const *item{signal_template_of(values["file"])};
|
||||
if (item == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST signal: no signal template " + values["file"]);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
for (int k = 0; k < static_cast<int>(tool.templates.size()); ++k)
|
||||
if (&tool.templates[k] == item)
|
||||
{
|
||||
static char const *const kinds[] = {"main", "block", "warning", "shunt", "stop", "substitute", "repeater"};
|
||||
for (int i = 0; i < 7; ++i)
|
||||
if (item->kind == kinds[i])
|
||||
{
|
||||
tool.kind = i;
|
||||
tool.chosen[i] = k;
|
||||
}
|
||||
}
|
||||
if (values.count("name"))
|
||||
std::snprintf(tool.name, sizeof(tool.name), "%s", values["name"].c_str());
|
||||
if (values.count("plate"))
|
||||
std::snprintf(tool.plate, sizeof(tool.plate), "%s", values["plate"].c_str());
|
||||
auto const side{values["side"] == "left" ? -1 : 1};
|
||||
auto const facing{values["facing"] == "along" ? 1 : values["facing"] == "against" ? -1 : 0};
|
||||
signal_spot spot;
|
||||
if (false == spot_on(*signal_armed(), facing, side, spot))
|
||||
{
|
||||
WriteLog("SELFTEST signal: no such track " + values["track"]);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const placed{signal_place(spot, *signal_armed())};
|
||||
WriteLog(format("SELFTEST signal %s: %s", placed ? "placed" : "failed", tool.status.c_str()));
|
||||
}
|
||||
else if (command == "signalmove")
|
||||
{
|
||||
auto const *standing{standing_named(values["name"])};
|
||||
auto const *item{standing != nullptr ? signal_template_of(standing->include.file) : nullptr};
|
||||
if (item == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST signalmove: no signal " + values["name"]);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const yaw{glm::radians(standing->include.yaw + 180.0)};
|
||||
glm::dvec2 const travel{std::sin(yaw), std::cos(yaw)};
|
||||
signal_spot probe;
|
||||
if (false == spot_on(*item, 1, 1, probe))
|
||||
{
|
||||
WriteLog("SELFTEST signalmove: no such track " + values["track"]);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const facing{glm::dot(probe.travel, travel) >= 0.0 ? 1 : -1};
|
||||
auto const sideoftravel{glm::dot(geometry::plan_of(standing->include.location) - geometry::plan_of(standing->spot.axis), glm::dvec2{-travel.y, travel.x}) >= 0.0 ? 1 : -1};
|
||||
signal_select(standing->include.instance);
|
||||
tool.moving = true;
|
||||
tool.moved_valid = spot_on(*item, facing, sideoftravel * facing, tool.moved);
|
||||
tool.pressed = {-1000.f, -1000.f};
|
||||
signal_release();
|
||||
WriteLog("SELFTEST signalmove: " + tool.status);
|
||||
}
|
||||
else if (command == "signaldelete")
|
||||
{
|
||||
auto const *standing{standing_named(values["name"])};
|
||||
auto const deleted{standing != nullptr && signal_delete(standing->include.instance)};
|
||||
WriteLog(std::string{"SELFTEST signaldelete "} + values["name"] + (deleted ? ": removed with its event" : ": not removed by the signal tool"));
|
||||
}
|
||||
else if (command == "signalrename")
|
||||
{
|
||||
auto const *standing{standing_named(values["name"])};
|
||||
if (standing == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST signalrename: no signal " + values["name"]);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
select_include(standing->include.instance);
|
||||
m_include.values[0] = values["to"];
|
||||
apply_include();
|
||||
m_include_gesture = false;
|
||||
WriteLog("SELFTEST signalrename " + values["name"] + " -> " + values["to"]);
|
||||
}
|
||||
else
|
||||
{
|
||||
signal_refresh(true);
|
||||
WriteLog(format("SELFTEST signalprobe: %zu signals", tool.standing.size()));
|
||||
for (auto const &standing : tool.standing)
|
||||
{
|
||||
std::string where{"not read"};
|
||||
if (standing.track != nullptr)
|
||||
{
|
||||
auto const &curve{standing.track->m_paths.front()};
|
||||
geometry::bezier const bez{curve};
|
||||
auto best{std::numeric_limits<double>::max()};
|
||||
double parameter{0.0};
|
||||
for (int k = 0; k <= 1024; ++k)
|
||||
{
|
||||
auto const d{geometry::plan_distance(bez.point(k / 1024.0), standing.include.location)};
|
||||
if (d < best)
|
||||
{
|
||||
best = d;
|
||||
parameter = k / 1024.0;
|
||||
}
|
||||
}
|
||||
auto const axis{bez.point(parameter)};
|
||||
auto tangent{bez.first(parameter)};
|
||||
glm::dvec2 travel{tangent.x, tangent.z};
|
||||
travel = glm::normalize(travel) * (standing.list == 1 ? -1.0 : 1.0);
|
||||
glm::dvec2 const offset{standing.include.location.x - axis.x, standing.include.location.z - axis.z};
|
||||
auto const cross{travel.x * offset.y - travel.y * offset.x};
|
||||
auto heading{glm::degrees(std::atan2(travel.x, travel.y))};
|
||||
auto turn{std::fmod(standing.include.yaw - heading + 720.0, 360.0)};
|
||||
auto const &list{editor_track::events(*standing.track, standing.list)};
|
||||
auto const bound{std::any_of(list.begin(), list.end(), [&](auto const &Event) { return Event.second != nullptr && ToLower(Event.first) == ToLower(standing.read); })};
|
||||
where = std::string{bound ? "bound, " : "unbound, "} + format("%s of %s, %.3f m from the axis, %s of the trains, %.3f m over the axis, model turned %.1f deg from the travel", editor_track::event_keyword(standing.list), standing.track->name().c_str(), glm::length(offset), cross > 0.0 ? "right" : "left",
|
||||
standing.include.location.y - axis.y, turn);
|
||||
}
|
||||
WriteLog(format("SELFTEST signalprobe %s %s at %.3f %.3f %.3f yaw %.2f read %s: %s", standing.name.c_str(), standing.include.file.c_str(), standing.include.location.x, standing.include.location.y, standing.include.location.z, standing.include.yaw,
|
||||
standing.read.empty() ? "-" : standing.read.c_str(), where.c_str()));
|
||||
}
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
return selftest_result::unhandled;
|
||||
}
|
||||
164
application/editormodeselftestsleepers.cpp
Normal file
164
application/editormodeselftestsleepers.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
/*
|
||||
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/editorSettings.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "application/application.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Event.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Sweep.h"
|
||||
#include "input/keyboardinput.h"
|
||||
#include "launcher/textures_scanner.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <algorithm>
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "scene/sceneterrain.h"
|
||||
#include "editor/editorSpeed.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include "application/editormodeselftest.h"
|
||||
|
||||
#include "world/TrackSleepers.h"
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_sleepers(std::string const &command, std::istringstream &words)
|
||||
{
|
||||
if (command == "beams")
|
||||
{
|
||||
// the beams the track lays, between two distances along it: their middles, lengths and the way they lie square to
|
||||
std::string name;
|
||||
double from{-1e9}, to{1e9};
|
||||
words >> name >> from >> to;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
if (track == nullptr || false == track->m_sleepers3d.active)
|
||||
{
|
||||
WriteLog("SELFTEST beams: no track with beams " + name);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
std::string text;
|
||||
for (auto const &beam : track_sleepers::place(*track))
|
||||
{
|
||||
auto const middle{0.5 * (beam.from + beam.to)};
|
||||
if (middle.z < from || middle.z > to)
|
||||
continue;
|
||||
text += format(" [%.2f %.2f L%.2f a%.2f%s]", middle.x, middle.z, glm::length(beam.to - beam.from), glm::degrees(std::atan2(beam.along.x, beam.along.z)), beam.plain ? " p" : "");
|
||||
}
|
||||
WriteLog("SELFTEST beams " + name + ":" + text);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "sharedzones")
|
||||
{
|
||||
// how far the timbers shared with other tracks reach along the paths of the track and of the tracks joined to its ends
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
if (track == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST sharedzones: no track " + name);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const zones_text = [](TTrack const &Track, int const Path) {
|
||||
std::string text;
|
||||
for (auto const &zone : track_sleepers::shared_zones(Track, Path))
|
||||
text += format(" %.2f..%.2f", zone.first, zone.second);
|
||||
return text.empty() ? std::string{" none"} : text;
|
||||
};
|
||||
auto const paths{track->eType == tt_Switch ? 2 : 1};
|
||||
for (int path = 0; path < paths; ++path)
|
||||
{
|
||||
auto const length{track->eType == tt_Switch ? track->SwitchExtension->Segments[path]->GetLength() : track->Segment->GetLength()};
|
||||
WriteLog(format("SELFTEST sharedzones %s path %d (%.2f m):%s", name.c_str(), path, length, zones_text(*track, path).c_str()));
|
||||
// the track going on from the end of the path, and how far past the end the shared timbers reach along it
|
||||
auto *next{track->eType == tt_Switch ? track->SwitchExtension->pNexts[path] : track->trNext};
|
||||
if (next == nullptr || next->eType != tt_Normal || next->Segment == nullptr)
|
||||
continue;
|
||||
glm::dvec3 end, start, unused;
|
||||
glm::vec3 angles;
|
||||
(track->eType == tt_Switch ? track->SwitchExtension->Segments[path] : track->Segment)->RaPositionGet(length, end, angles);
|
||||
next->Segment->RaPositionGet(0.0, start, angles);
|
||||
auto const fromstart{glm::length(start - end) < 0.1};
|
||||
auto past{0.0};
|
||||
for (auto const &zone : track_sleepers::shared_zones(*next, 0))
|
||||
{
|
||||
if (fromstart && zone.first < 0.05)
|
||||
past = zone.second;
|
||||
else if (false == fromstart && zone.second > next->Segment->GetLength() - 0.05)
|
||||
past = next->Segment->GetLength() - zone.first;
|
||||
}
|
||||
WriteLog(format("SELFTEST sharedzones %s path %d goes on into %s:%s; shared timbers reach %.2f m past the end", name.c_str(), path, next->name().c_str(), zones_text(*next, 0).c_str(), past));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "sleepersbg")
|
||||
{
|
||||
// the sleepers worked out in the background as in the simulation, to see they come out the same, or back to as they're needed
|
||||
int on{1};
|
||||
words >> on;
|
||||
track_sleepers::lay_in_background(on != 0);
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
if (track != nullptr)
|
||||
track->m_sleepers3d.dirty = true;
|
||||
track_sleepers::unsettle_around(*simulation::Paths.sequence().front());
|
||||
WriteLog(format("SELFTEST sleepersbg %d", on));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "beamsnear")
|
||||
{
|
||||
double x{0.0}, z{0.0}, radius{3.0};
|
||||
words >> x >> z >> radius;
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || false == track->m_sleepers3d.active)
|
||||
continue;
|
||||
for (auto const &beam : track_sleepers::place(*track))
|
||||
{
|
||||
auto const middle{0.5 * (beam.from + beam.to)};
|
||||
if (glm::length(glm::dvec2{middle.x - x, middle.z - z}) > radius)
|
||||
continue;
|
||||
WriteLog(format("SELFTEST beam %s: %.2f %.2f -> %.2f %.2f, %.2f m, along %.1f deg%s", track->name().c_str(), beam.from.x, beam.from.z, beam.to.x, beam.to.z, glm::length(beam.to - beam.from), glm::degrees(std::atan2(beam.along.x, beam.along.z)), beam.plain ? " plain" : ""));
|
||||
}
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "sleepercheck")
|
||||
{
|
||||
auto const start{std::chrono::steady_clock::now()};
|
||||
auto const result{track_sleepers::check()};
|
||||
WriteLog(format("SELFTEST sleepercheck: %zu beams, %zu overlaps, %zu gaps, %zu bare rails, %zu too long (longest %.2f m), widest spacing %.2f m at %s; %zu shared off one plane, %zu switch panels off one plane, %zu ends clipped, %zu beams dropped, slowest cluster %.1f ms of %zu tracks; digest %016llx; in %.1f ms",
|
||||
result.beams, result.overlaps, result.gaps, result.bare, result.toolong, result.longest, result.widest, result.widestat.c_str(), result.offplane, result.panels, result.clipped, result.dropped, result.slowest, result.biggest, static_cast<unsigned long long>(result.digest),
|
||||
std::chrono::duration<double, std::milli>(std::chrono::steady_clock::now() - start).count()));
|
||||
for (auto const &example : result.examples)
|
||||
WriteLog("SELFTEST sleepercheck " + example);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
return selftest_result::unhandled;
|
||||
}
|
||||
429
application/editormodeselftestsweep.cpp
Normal file
429
application/editormodeselftestsweep.cpp
Normal file
@@ -0,0 +1,429 @@
|
||||
/*
|
||||
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/editorSettings.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "application/application.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Event.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Sweep.h"
|
||||
#include "input/keyboardinput.h"
|
||||
#include "launcher/textures_scanner.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <algorithm>
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "scene/sceneterrain.h"
|
||||
#include "editor/editorSpeed.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include "application/editormodeselftest.h"
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_sweep(std::string const &command, std::istringstream &words)
|
||||
{
|
||||
if (command == "sweep")
|
||||
{
|
||||
std::map<std::string, std::string> values;
|
||||
std::string pair;
|
||||
while (words >> pair)
|
||||
{
|
||||
auto const equals{pair.find('=')};
|
||||
if (equals != std::string::npos)
|
||||
values[pair.substr(0, equals)] = pair.substr(equals + 1);
|
||||
}
|
||||
auto &tool{m_sweep};
|
||||
auto const get = [&](char const *Key, std::string const &Default) { auto const found{values.find(Key)}; return found != values.end() ? found->second : Default; };
|
||||
tool.edited = nullptr;
|
||||
tool.curve_for = nullptr;
|
||||
std::snprintf(tool.model, sizeof(tool.model), "%s", get("model", "").c_str());
|
||||
tool.parameters.clear();
|
||||
std::stringstream parameters(get("params", ""));
|
||||
std::string parameter;
|
||||
while (std::getline(parameters, parameter, ','))
|
||||
{
|
||||
tool.parameters.emplace_back();
|
||||
std::snprintf(tool.parameters.back().data(), tool.parameters.back().size(), "%s", parameter.c_str());
|
||||
}
|
||||
tool.parameters_for = tool.model;
|
||||
auto &settings{tool.settings};
|
||||
settings.lateral = std::stod(get("lateral", "0"));
|
||||
settings.height = std::stod(get("height", "0"));
|
||||
auto const side{get("side", "origin")};
|
||||
settings.side_anchor = side == "near" ? 1 : side == "far" ? 2 : side == "centre" ? 3 : 0;
|
||||
auto const top{get("top", "origin")};
|
||||
settings.height_anchor = top == "bottom" ? 1 : top == "top" ? 2 : top == "edge" ? 3 : 0;
|
||||
settings.bend = get("mode", "bend") != "repeat";
|
||||
settings.step = std::stod(get("step", "0"));
|
||||
settings.widen = get("widen", "0") == "1";
|
||||
settings.platform = get("platform", "0") == "1";
|
||||
settings.face = get("face", "1") == "1";
|
||||
settings.tilt = get("tilt", "0") == "1";
|
||||
settings.from = std::stod(get("from", "0"));
|
||||
settings.to = std::stod(get("to", "-1"));
|
||||
auto const length{get("length", "all")};
|
||||
tool.length_mode = length == "original" ? 0 : length == "all" ? 2 : 1;
|
||||
if (tool.length_mode == 1)
|
||||
tool.length = std::stod(length);
|
||||
if (values.count("edge"))
|
||||
EditorSettings.platform_edge(std::stod(values["edge"]));
|
||||
tool.measured.clear();
|
||||
tool.open = true;
|
||||
sweep_curve();
|
||||
sweep_measure();
|
||||
if (values.count("widen"))
|
||||
settings.widen = get("widen", "0") == "1";
|
||||
if (values.count("platform"))
|
||||
settings.platform = get("platform", "0") == "1";
|
||||
if (get("create", "1") == "1")
|
||||
{
|
||||
sweep_create();
|
||||
WriteLog("SELFTEST sweep: " + tool.status);
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "drag")
|
||||
{
|
||||
glm::dvec3 from{0.0};
|
||||
glm::dvec3 to{0.0};
|
||||
words >> from.x >> from.z >> to.x >> to.z;
|
||||
m_sweep.open = true;
|
||||
if (false == sweep_press_at(from, false))
|
||||
{
|
||||
WriteLog("SELFTEST drag: the press wasn't taken");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
sweep_drag_at(to);
|
||||
sweep_release();
|
||||
WriteLog("SELFTEST drag: " + m_sweep.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "fouling")
|
||||
{
|
||||
std::map<std::string, std::string> values;
|
||||
std::string pair;
|
||||
while (words >> pair)
|
||||
{
|
||||
auto const equals{pair.find('=')};
|
||||
if (equals != std::string::npos)
|
||||
values[pair.substr(0, equals)] = pair.substr(equals + 1);
|
||||
}
|
||||
auto &tool{m_fouling};
|
||||
if (values["show"] == "1")
|
||||
{
|
||||
show_track_tab(track_tab::lineside);
|
||||
tool.expand = true;
|
||||
}
|
||||
tool.scope = std::stoi(values.count("scope") ? values["scope"] : "2");
|
||||
tool.height_mode = values["mode"] == "ground" ? 0 : 1;
|
||||
tool.height = std::stod(values.count("height") ? values["height"] : "0");
|
||||
tool.dirty = true;
|
||||
fouling_update();
|
||||
WriteLog("SELFTEST fouling: " + tool.status);
|
||||
int shown{0};
|
||||
for (auto const &found : tool.found)
|
||||
if (found.wrong && shown++ < 5)
|
||||
WriteLog(format("SELFTEST fouling: %s %.2f m off, %+.3f m high", found.marker.file.c_str(), found.error, found.height_error));
|
||||
if (values["place"] == "one")
|
||||
{
|
||||
// the first fouling point without a marker alone, as a click on it in the view
|
||||
for (int index = 0; index < static_cast<int>(tool.points.size()); ++index)
|
||||
if (tool.points[index].standing < 0)
|
||||
{
|
||||
fouling_place(index);
|
||||
break;
|
||||
}
|
||||
tool.dirty = true;
|
||||
fouling_update();
|
||||
WriteLog("SELFTEST fouling one: " + tool.status);
|
||||
}
|
||||
if (values["place"] == "1")
|
||||
{
|
||||
fouling_place();
|
||||
tool.dirty = true;
|
||||
fouling_update();
|
||||
WriteLog("SELFTEST fouling after placing: " + tool.status);
|
||||
}
|
||||
if (values["fix"] == "1")
|
||||
{
|
||||
fouling_fix();
|
||||
tool.dirty = true;
|
||||
fouling_update();
|
||||
WriteLog("SELFTEST fouling after the fix: " + tool.status);
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "hekto")
|
||||
{
|
||||
std::map<std::string, std::string> values;
|
||||
std::string pair;
|
||||
while (words >> pair)
|
||||
{
|
||||
auto const equals{pair.find('=')};
|
||||
if (equals != std::string::npos)
|
||||
values[pair.substr(0, equals)] = pair.substr(equals + 1);
|
||||
}
|
||||
auto *track{simulation::Paths.find(values["track"])};
|
||||
if (track == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST hekto: no such track");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
hekto_start(*track, track->m_paths.front().points[segment_data::point::start]);
|
||||
auto &tool{m_hekto};
|
||||
tool.dirty = true;
|
||||
hekto_update();
|
||||
if (values.count("km"))
|
||||
{
|
||||
tool.km = std::stod(values["km"]);
|
||||
tool.growth = values["growth"] == "-1" ? -1 : 1;
|
||||
}
|
||||
tool.side = values["side"] == "alt" ? 0 : values["side"] == "left" ? -1 : 1;
|
||||
tool.dirty = true;
|
||||
hekto_update();
|
||||
auto const missing{std::count_if(tool.posts.begin(), tool.posts.end(), [](hekto_post const &Post) { return Post.standing < 0; })};
|
||||
WriteLog(format("SELFTEST hekto: km %.3f growth %d, %zu posts along %.0f m, %d to place, %zu standing. %s", tool.km, tool.growth, tool.posts.size(), tool.samples.empty() ? 0.0 : tool.samples.back().chainage, static_cast<int>(missing), tool.found.size(), tool.status.c_str()));
|
||||
for (std::size_t i = 0; i < tool.posts.size() && i < 4; ++i)
|
||||
{
|
||||
auto const &post{tool.posts[i]};
|
||||
double lateral;
|
||||
glm::dvec3 const axis{editor_track::sampled_position(tool.samples, post.chainage)};
|
||||
auto const direction{editor_track::sampled_position(tool.samples, post.chainage + 1.0) - axis};
|
||||
glm::dvec2 const offset{post.position.x - axis.x, post.position.z - axis.z};
|
||||
lateral = glm::length(offset);
|
||||
auto const right{(direction.x * offset.y - direction.z * offset.x) * tool.growth >= 0.0};
|
||||
WriteLog(format("SELFTEST hekto post %d.%d at %.1f m, %.2f m from the axis, %s of the growing kilometrage, at %.1f %.1f %.1f", post.hectometres / 10, post.hectometres % 10, post.chainage, lateral, right ? "right" : "left", post.position.x, post.position.y,
|
||||
post.position.z));
|
||||
}
|
||||
if (values["place"] == "1")
|
||||
{
|
||||
hekto_place();
|
||||
WriteLog("SELFTEST hekto: " + tool.status);
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "spread")
|
||||
{
|
||||
std::string name;
|
||||
words >> name >> m_spread.stop >> m_spread.reach;
|
||||
if (auto *track{simulation::Paths.find(name)})
|
||||
{
|
||||
std::string names;
|
||||
for (auto const *other : track_spread_from(*track))
|
||||
names += ' ' + other->name();
|
||||
WriteLog("SELFTEST spread " + name + ":" + names + " | prev " + (track->trPrev ? track->trPrev->name() : std::string{"-"}) + " next " + (track->trNext ? track->trNext->name() : std::string{"-"}));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "turntable")
|
||||
{
|
||||
glm::dvec3 centre{0.0};
|
||||
double yaw{0.0};
|
||||
words >> centre.x >> centre.y >> centre.z >> yaw;
|
||||
turntable_place(centre, yaw);
|
||||
for (double angle; words >> angle;)
|
||||
{
|
||||
m_turntable.heading = m_turntable.table_yaw + angle;
|
||||
m_turntable.exit_length = 10.0;
|
||||
track_intent intent;
|
||||
intent.what = track_intent::kind::turntable_exit;
|
||||
turntable_press(intent);
|
||||
}
|
||||
WriteLog("SELFTEST turntable: " + (m_turntable.error.empty() ? m_turntable.status : m_turntable.error));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "bend" || command == "bendset")
|
||||
{
|
||||
std::map<std::string, std::string> values;
|
||||
std::string pair;
|
||||
while (words >> pair)
|
||||
{
|
||||
auto const equals{pair.find('=')};
|
||||
if (equals != std::string::npos)
|
||||
values[pair.substr(0, equals)] = pair.substr(equals + 1);
|
||||
}
|
||||
auto const vector = [](std::string Text) {
|
||||
std::replace(Text.begin(), Text.end(), ',', ' ');
|
||||
std::istringstream numbers(Text);
|
||||
glm::dvec3 result{0.0};
|
||||
numbers >> result.x >> result.y >> result.z;
|
||||
return result;
|
||||
};
|
||||
auto const index = [](std::string const &Text, std::vector<std::string> const &Names) {
|
||||
return static_cast<int>(std::find(Names.begin(), Names.end(), Text) - Names.begin());
|
||||
};
|
||||
auto &tool{m_bend};
|
||||
auto const along{values.count("along") ? index(values["along"], {"start", "origin", "middle", "end", "point"}) : tool.along_anchor};
|
||||
auto const side{values.count("side") ? index(values["side"], {"origin", "near", "far", "middle", "point"}) : tool.side_anchor};
|
||||
auto const height{values.count("height") ? index(values["height"], {"origin", "bottom", "top", "edge", "point"}) : tool.height_anchor};
|
||||
auto const point{values.count("point") ? vector(values["point"]) : tool.point};
|
||||
if (command == "bend")
|
||||
{
|
||||
tool.edited = nullptr;
|
||||
tool.along_anchor = along;
|
||||
tool.side_anchor = side;
|
||||
tool.height_anchor = height;
|
||||
tool.point = point;
|
||||
tool.axis = values.count("axis") ? index(values["axis"], {"auto", "z", "x"}) : 0;
|
||||
tool.turned = values["turned"] == "1";
|
||||
bend_source source;
|
||||
if (false == bend_source_of_selection(source))
|
||||
{
|
||||
std::string reason;
|
||||
auto const editable{m_node != nullptr && scene::Layers.editable(m_node, &reason)};
|
||||
WriteLog(format("SELFTEST bend: nothing to bend (node %p template %d editable %d %s, instance %d)", static_cast<void *>(m_node), m_node != nullptr && m_node->from_template() ? 1 : 0, editable ? 1 : 0, reason.c_str(), static_cast<int>(m_instance)));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
tool.tracks_for = nullptr;
|
||||
bend_find_tracks(glm::dvec3(source.transform[3]), source.model != nullptr ? static_cast<void const *>(source.model) : reinterpret_cast<void const *>(static_cast<std::uintptr_t>(source.instance)));
|
||||
tool.track = values.count("track") ? std::stoi(values["track"]) : 0;
|
||||
if (values.count("pick"))
|
||||
bend_pick(vector(values["pick"]));
|
||||
if (tool.tracks.empty())
|
||||
{
|
||||
WriteLog("SELFTEST bend: no track nearby");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
sweep_node::state state;
|
||||
std::vector<TTrack *> tracks;
|
||||
bend_definition(source, *tool.tracks[tool.track].first, state, tracks);
|
||||
WriteLog(format("SELFTEST bend: track %s, %zu paths, anchor before %.3f %.3f %.3f, lateral %.3f height %.3f from %.3f to %.3f, axis %s flip %d; %s", tool.tracks[tool.track].first->name().c_str(), tracks.size(),
|
||||
tool.marker.x, tool.marker.y, tool.marker.z, state.lateral, state.height, state.from, state.to, state.along_x ? "x" : "z", state.flip ? 1 : 0, tool.status.c_str()));
|
||||
if (values["go"] == "1")
|
||||
{
|
||||
bend_selection();
|
||||
WriteLog("SELFTEST bend: " + tool.status);
|
||||
}
|
||||
}
|
||||
else if (auto *sweep{tool.edited})
|
||||
{
|
||||
auto state{sweep->definition()};
|
||||
tool.along_anchor = along;
|
||||
if (side != tool.side_anchor || height != tool.height_anchor || point != tool.point)
|
||||
{
|
||||
tool.side_anchor = side;
|
||||
tool.height_anchor = height;
|
||||
tool.point = point;
|
||||
bend_reanchor(state, side, height, point);
|
||||
}
|
||||
if (values.count("lateral"))
|
||||
state.lateral = std::stod(values["lateral"]);
|
||||
if (values.count("up"))
|
||||
state.height = std::stod(values["up"]);
|
||||
if (values.count("widen"))
|
||||
state.widen = values["widen"] == "1";
|
||||
if (values.count("pick"))
|
||||
{
|
||||
bend_pick(vector(values["pick"]));
|
||||
state = sweep->definition();
|
||||
}
|
||||
if (values.count("length"))
|
||||
{
|
||||
bend_stretch(*sweep, bend_fixed(*sweep, state.point), std::stod(values["length"]), state.point, state);
|
||||
}
|
||||
bend_edit(state);
|
||||
WriteLog("SELFTEST bendset done");
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "bendprobe")
|
||||
{
|
||||
auto *sweep{m_bend.edited};
|
||||
if (sweep == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST bendprobe: nothing bent");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
glm::dvec3 low;
|
||||
glm::dvec3 high;
|
||||
glm::dvec3 shift;
|
||||
sweep->bounds(low, high, shift);
|
||||
auto const &state{sweep->definition()};
|
||||
auto const marker{bend_marker(*sweep)};
|
||||
glm::dvec3 boxlow{std::numeric_limits<double>::max()};
|
||||
glm::dvec3 boxhigh{-std::numeric_limits<double>::max()};
|
||||
std::size_t vertices{0};
|
||||
for (auto const &shape : sweep->create_shapes())
|
||||
for (auto const &vertex : shape.data().vertices)
|
||||
{
|
||||
boxlow = glm::min(boxlow, vertex.position);
|
||||
boxhigh = glm::max(boxhigh, vertex.position);
|
||||
++vertices;
|
||||
}
|
||||
WriteLog(format("SELFTEST bendprobe %s: stations %.3f - %.3f, along the reference line %.3f m, model %.3f m, lateral %.3f height %.3f anchors %d/%d point %.3f %.3f %.3f, marker %.3f %.3f %.3f, box %.2f %.2f %.2f - %.2f %.2f %.2f, %zu vertices, removed %d",
|
||||
sweep->name().c_str(), sweep->start(), sweep->end(), sweep->distance_at(sweep->end()) - sweep->distance_at(sweep->start()), high.x - low.x, state.lateral, state.height, state.side_anchor, state.height_anchor, state.point.x,
|
||||
state.point.y, state.point.z, marker.x, marker.y, marker.z, boxlow.x, boxlow.y, boxlow.z, boxhigh.x, boxhigh.y, boxhigh.z, vertices, sweep->m_editorremoved ? 1 : 0));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "lineside")
|
||||
{
|
||||
std::string tab;
|
||||
words >> tab;
|
||||
show_track_tab(tab == "parallel" ? track_tab::parallel : track_tab::lineside);
|
||||
(tab == "vehicle" ? m_vehicle.expand : tab == "hekto" ? m_hekto.expand : tab == "fouling" ? m_fouling.expand : m_parallel.expand) = true;
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "bendpanel")
|
||||
{
|
||||
ui()->expand_bend();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "gauge")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
if (auto *track{simulation::Paths.find(name)})
|
||||
{
|
||||
scan_gauge(*track);
|
||||
WriteLog(format("SELFTEST gauge %s: %d hits", name.c_str(), static_cast<int>(m_gauge.line.hits.size())));
|
||||
for (auto const &hit : m_gauge.line.hits)
|
||||
WriteLog(format("SELFTEST gauge hit %s depth %.3f at %.2f %.2f %.2f", hit.model.c_str(), hit.depth, hit.point.x, hit.point.y, hit.point.z));
|
||||
}
|
||||
else
|
||||
WriteLog("SELFTEST gauge " + name + ": no such track");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "ttincludes")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto const *track{simulation::Paths.find(name)};
|
||||
WriteLog(format("SELFTEST ttincludes %s: track %s, includes %d", name.c_str(), track == nullptr ? "none" : track->m_editorremoved ? "removed" : "present",
|
||||
track != nullptr ? static_cast<int>(turntable_includes(*track).size()) : -1));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "checkspans")
|
||||
{
|
||||
WriteLog("SELFTEST spans: " + scene::Layers.check_sources());
|
||||
return selftest_result::handled;
|
||||
}
|
||||
return selftest_result::unhandled;
|
||||
}
|
||||
771
application/editormodeselftestswitch.cpp
Normal file
771
application/editormodeselftestswitch.cpp
Normal file
@@ -0,0 +1,771 @@
|
||||
/*
|
||||
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/editorSettings.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "application/application.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Event.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Sweep.h"
|
||||
#include "input/keyboardinput.h"
|
||||
#include "launcher/textures_scanner.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <algorithm>
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "scene/sceneterrain.h"
|
||||
#include "editor/editorSpeed.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include "application/editormodeselftest.h"
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_switch(std::string const &command, std::istringstream &words)
|
||||
{
|
||||
if (command == "curvedswitch")
|
||||
{
|
||||
std::string name;
|
||||
std::string label;
|
||||
glm::dvec3 at{0.0};
|
||||
glm::dvec3 toward{0.0};
|
||||
words >> name >> at.x >> at.z >> toward.x >> toward.z >> label;
|
||||
std::replace(label.begin(), label.end(), '_', ' ');
|
||||
m_node = simulation::Paths.find(name);
|
||||
ui()->set_node(m_node);
|
||||
show_track_tab(track_tab::turnout);
|
||||
arm_switch();
|
||||
auto &tool{m_switch};
|
||||
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
||||
if (false == label.empty() && tool.templates[i].label.find(label) != std::string::npos)
|
||||
{
|
||||
tool.armed = i;
|
||||
break;
|
||||
}
|
||||
m_cursor_override = at;
|
||||
auto const started{start_switch_placement()};
|
||||
m_cursor_override.reset();
|
||||
if (false == started)
|
||||
{
|
||||
WriteLog("SELFTEST curvedswitch: not started, " + tool.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
tool.mouse = glm::dvec3{toward.x, tool.point.y, toward.z};
|
||||
finish_switch_placement();
|
||||
WriteLog("SELFTEST curvedswitch " + (tool.armed >= 0 ? tool.templates[tool.armed].label : std::string{}) + ": " + tool.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "designer")
|
||||
{
|
||||
std::string list;
|
||||
int index{0};
|
||||
words >> list >> index;
|
||||
auto &designer{m_switch_designer};
|
||||
designer.open = list != "close";
|
||||
designer.from_catalogue = list != "own";
|
||||
designer.selected = index;
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "release")
|
||||
{
|
||||
m_switch.placing = false;
|
||||
lay_cancel();
|
||||
m_cursor_override.reset();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "hover")
|
||||
{
|
||||
glm::dvec3 at{0.0};
|
||||
if (words >> at.x >> at.z)
|
||||
m_cursor_override = at;
|
||||
else
|
||||
m_cursor_override.reset();
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "layhold")
|
||||
{
|
||||
int layout{0};
|
||||
words >> layout >> m_lay.radius;
|
||||
m_track_layout = static_cast<track_layout>(std::clamp(layout, 0, 3));
|
||||
m_lay.active = true;
|
||||
lay_cancel();
|
||||
std::vector<glm::dvec3> points;
|
||||
for (glm::dvec3 point{0.0}; words >> point.x >> point.z;)
|
||||
points.push_back(point);
|
||||
for (std::size_t i = 0; i + 1 < points.size(); ++i)
|
||||
{
|
||||
auto point{points[i]};
|
||||
point.y = 5000.0;
|
||||
std::vector<char> found;
|
||||
auto const ground{ground_heights({point}, true, &found)};
|
||||
point.y = found[0] != 0 ? ground[0] : m_lay.points.empty() ? 0.0 : m_lay.points.back().y;
|
||||
m_lay.mouse_snap = editor_track::find_free_end(nullptr, 1, point, 3.0, {});
|
||||
m_lay.mouse = m_lay.mouse_snap.track != nullptr ? point : lay_bed(point);
|
||||
lay_click();
|
||||
}
|
||||
if (false == points.empty())
|
||||
m_cursor_override = points.back();
|
||||
WriteLog("SELFTEST layhold: " + m_lay.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switchkind")
|
||||
{
|
||||
// what the switch tool places, as chosen in its panel: single, slip, crossing or connection with the kind of it
|
||||
std::string word;
|
||||
int sub{0};
|
||||
words >> word >> sub;
|
||||
auto const kind{word == "slip" ? switch_kind::slip : word == "crossing" ? switch_kind::crossing : word == "connection" ? switch_kind::connection : switch_kind::single};
|
||||
m_switch.connection_kind = sub;
|
||||
choose_switch_kind(kind);
|
||||
WriteLog(format("SELFTEST switchkind %s: kind %d, connection %d, armed %d", word.c_str(), static_cast<int>(current_switch_kind()), m_switch.connection, m_switch.armed));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "connection" || command == "connectionplan" || command == "connectionhold")
|
||||
{
|
||||
std::string kindword;
|
||||
std::string label;
|
||||
std::string second;
|
||||
glm::dvec3 at{0.0};
|
||||
glm::dvec3 toward{0.0};
|
||||
words >> kindword >> at.x >> at.z >> toward.x >> toward.z >> label >> second;
|
||||
connection::kind kind{connection::kind::crossover};
|
||||
if (kindword != "single" && false == connection::parse(kindword, kind))
|
||||
{
|
||||
WriteLog("SELFTEST connection: unknown kind " + kindword);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto &tool{m_switch};
|
||||
for (std::string key; words >> key;)
|
||||
{
|
||||
if (key == "radius")
|
||||
words >> tool.connection_radius;
|
||||
else if (key == "gap")
|
||||
words >> tool.connection_gap;
|
||||
else if (key == "together")
|
||||
words >> tool.connection_together;
|
||||
else if (key == "stub")
|
||||
words >> tool.connection_stub;
|
||||
else if (key == "spacings")
|
||||
{
|
||||
std::string value;
|
||||
words >> value;
|
||||
std::snprintf(tool.connection_spacings, sizeof(tool.connection_spacings), "%s", value.c_str());
|
||||
}
|
||||
}
|
||||
show_track_tab(track_tab::turnout);
|
||||
arm_switch();
|
||||
auto const find = [&](std::string Label) {
|
||||
std::replace(Label.begin(), Label.end(), '_', ' ');
|
||||
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
||||
if (false == Label.empty() && Label != "-" && false == tool.templates[i].double_slip && false == tool.templates[i].twofold() && tool.templates[i].label.find(Label) != std::string::npos)
|
||||
return i;
|
||||
return -1;
|
||||
};
|
||||
auto const first{find(label)};
|
||||
if (first < 0)
|
||||
{
|
||||
WriteLog("SELFTEST connection: no template " + label);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
tool.armed = first;
|
||||
tool.connection_second = find(second);
|
||||
tool.connection = kindword == "single" ? -1 : static_cast<int>(kind);
|
||||
auto const line{straight_near(at, {}, 3.0)};
|
||||
if (line.tracks.empty())
|
||||
{
|
||||
WriteLog("SELFTEST connection: no straight at the start");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
m_node = line.tracks.front();
|
||||
ui()->set_node(m_node);
|
||||
m_cursor_override = at;
|
||||
auto const started{start_switch_placement()};
|
||||
m_cursor_override.reset();
|
||||
if (false == started)
|
||||
{
|
||||
WriteLog("SELFTEST connection: not started, " + tool.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
tool.mouse = glm::dvec3{toward.x, tool.point.y, toward.z};
|
||||
if (command == "connectionhold")
|
||||
{
|
||||
m_cursor_override = toward;
|
||||
WriteLog("SELFTEST connectionhold: " + tool.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
connection::request request;
|
||||
connection::result plan;
|
||||
auto const valid{plan_connection(request, plan)};
|
||||
WriteLog("SELFTEST connection " + kindword + ": " + (valid ? connection_readout(plan) : "invalid, " + plan.error));
|
||||
for (auto const &entry : plan.switches)
|
||||
{
|
||||
auto const &start{entry.paths[0].points[segment_data::point::start]};
|
||||
auto const &end{entry.paths[1].points[segment_data::point::end]};
|
||||
WriteLog(format("SELFTEST connection switch on %d at %.3f, forward %d side %d%s: %.3f %.3f %.3f -> branch end %.3f %.3f %.3f", entry.line, entry.along, entry.forward, entry.side, entry.second ? " (second)" : "", start.x, start.y, start.z, end.x, end.y,
|
||||
end.z));
|
||||
}
|
||||
for (auto const &insert : plan.inserts)
|
||||
{
|
||||
auto const &start{insert.points[segment_data::point::start]};
|
||||
auto const &end{insert.points[segment_data::point::end]};
|
||||
WriteLog(format("SELFTEST connection insert %.3f %.3f %.3f -> %.3f %.3f %.3f, %.3f m, R %.1f", start.x, start.y, start.z, end.x, end.y, end.z, geometry::bezier{insert}.plan_length(), std::abs(insert.radius)));
|
||||
}
|
||||
if (command == "connectionplan" || false == valid)
|
||||
{
|
||||
tool.placing = false;
|
||||
return selftest_result::handled;
|
||||
}
|
||||
insert_connection();
|
||||
WriteLog("SELFTEST connection built: " + tool.status);
|
||||
for (auto const &entry : switch_store::records())
|
||||
if (entry.kind == "connection")
|
||||
{
|
||||
std::string parts;
|
||||
for (auto const &part : entry.parts)
|
||||
parts += ' ' + part;
|
||||
WriteLog(format("SELFTEST connection record %s %s: spacing %.3f, insert %.3f, parts%s", entry.type.c_str(), entry.name.c_str(), entry.a, entry.b, parts.c_str()));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "conecheck")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
if (track == nullptr || track->m_paths.size() < 2)
|
||||
{
|
||||
WriteLog("SELFTEST conecheck: no switch " + name);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
geometry::bezier const main{track->m_paths[0]};
|
||||
geometry::bezier const other{track->m_paths[1]};
|
||||
std::string text;
|
||||
for (int i = 1; i <= 8; ++i)
|
||||
{
|
||||
auto const t{i * 0.125};
|
||||
auto const point{other.point(t)};
|
||||
double best{std::numeric_limits<double>::max()};
|
||||
double along{0.0};
|
||||
for (int k = 0; k <= 2000; ++k)
|
||||
if (auto const distance{geometry::plan_distance(main.point(k / 2000.0), point)}; distance < best)
|
||||
{
|
||||
best = distance;
|
||||
along = k / 2000.0;
|
||||
}
|
||||
auto const base{main.point(along)};
|
||||
auto const direction{glm::normalize(geometry::plan_of(main.first(along)))};
|
||||
auto const lateral{(point.x - base.x) * -direction.y + (point.z - base.z) * direction.x};
|
||||
auto const &path{track->m_paths[0]};
|
||||
auto const roll{glm::radians(path.rolls[0] + (path.rolls[1] - path.rolls[0]) * along)};
|
||||
auto const tangent{main.first(along)};
|
||||
auto const beyond{(point.x - base.x) * direction.x + (point.z - base.z) * direction.y};
|
||||
auto const cone{base.y + beyond * tangent.y / glm::length(geometry::plan_of(tangent)) - lateral * std::tan(roll)};
|
||||
text += format(" [t %.3f off %.3f dy %.4f]", t, lateral, point.y - cone);
|
||||
}
|
||||
WriteLog("SELFTEST conecheck " + name + ":" + text);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "throw")
|
||||
{
|
||||
std::string name;
|
||||
int state{0};
|
||||
words >> name >> state;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
if (track != nullptr && track->eType == tt_Switch)
|
||||
{
|
||||
track->Switch(state, 0.f, 0.f);
|
||||
// the blades go over at once, the editor doesn't run the animations of the switches
|
||||
track->SwitchExtension->fOffset = track->SwitchExtension->fDesiredOffset;
|
||||
track->RaAnimate();
|
||||
}
|
||||
WriteLog(format("SELFTEST throw %s %d%s", name.c_str(), state, track != nullptr && track->eType == tt_Switch ? "" : ": no such switch"));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switchdrive")
|
||||
{
|
||||
int index{-1};
|
||||
int side{0};
|
||||
words >> index >> side;
|
||||
scan_switch_drives();
|
||||
m_switch.drive = index;
|
||||
m_switch.drive_side = side;
|
||||
WriteLog("SELFTEST switchdrive: " + (index >= 0 && index < static_cast<int>(m_switch.drives.size()) ? m_switch.drives[index].name : std::string{"none"}));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "drivechoice")
|
||||
{
|
||||
int index{-1};
|
||||
int side{0};
|
||||
words >> index >> side;
|
||||
scan_switch_drives();
|
||||
m_switch.drive = index;
|
||||
m_switch.drive_side = side;
|
||||
m_switch.status.clear();
|
||||
auto *track{selected_track()};
|
||||
if (track != nullptr && track->eType == tt_Switch && false == track->m_editorremoved)
|
||||
apply_switch_drive_choice(*track);
|
||||
WriteLog("SELFTEST drivechoice: " + (track != nullptr ? track->name() : std::string{"no switch"}) + ", " + m_switch.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "drives")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
scan_switch_drives();
|
||||
std::string files;
|
||||
for (auto const &standing : standing_templates([](std::string const &File) { return File.find("napedy/") != std::string::npos; }))
|
||||
if (false == standing.values.empty() && standing.values[0] == name)
|
||||
{
|
||||
files += " " + standing.file;
|
||||
for (std::size_t i = 1; i < standing.values.size(); ++i)
|
||||
files += (i == 1 ? " (" : " ") + standing.values[i] + (i + 1 == standing.values.size() ? ")" : "");
|
||||
}
|
||||
for (scene::instance_handle handle = 1; handle <= scene::Layers.instance_count(); ++handle)
|
||||
if (scene::Layers.tracked(handle) && scene::Layers.instance(handle).file != nullptr && scene::Layers.instance(handle).file->find("napedy") != std::string::npos)
|
||||
files += format(" [%d %s removed %d dead %d]", static_cast<int>(handle), scene::Layers.instance(handle).file->c_str(), scene::Layers.instance(handle).removed ? 1 : 0, scene::Layers.instance(handle).dead ? 1 : 0);
|
||||
WriteLog("SELFTEST drives " + name + ": chosen " + (m_switch.drive >= 0 ? m_switch.drives[m_switch.drive].name : std::string{"none"}) + ", side " + std::to_string(m_switch.drive_side) + ", standing" + files + ", steps " + std::to_string(m_history.size()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "driveflip")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
auto const done{track != nullptr && flip_switch_drive(*track)};
|
||||
WriteLog("SELFTEST driveflip " + name + ": " + (done ? "done " : "failed ") + m_switch.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "driveput" || command == "drivefamily")
|
||||
{
|
||||
std::string name;
|
||||
int family{-1};
|
||||
words >> name >> family;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
auto const done{track != nullptr && (command == "driveput" ? place_switch_drive(*track) : replace_switch_drive(*track, family, false))};
|
||||
WriteLog("SELFTEST " + command + " " + name + ": " + (done ? "done " : "failed ") + m_switch.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "drivetilt")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto *model{simulation::Instances.find(name)};
|
||||
auto *track{simulation::Paths.find(name.substr(0, name.find('_')))};
|
||||
if (model == nullptr || track == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST drivetilt: no " + name);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const &path{track->m_paths[0]};
|
||||
auto const transform{glm::dmat3(model->rotation_scale())};
|
||||
auto const across{transform * glm::dvec3{1.0, 0.0, 0.0}};
|
||||
auto const forward{transform * glm::dvec3{0.0, 0.0, 1.0}};
|
||||
auto const direction{glm::normalize(glm::dvec2{forward.x, forward.z})};
|
||||
auto const lateral{across.x * -direction.y + across.z * direction.x};
|
||||
auto const cone{-lateral * std::tan(glm::radians(static_cast<double>(path.rolls[0])))};
|
||||
auto const control{path.points[segment_data::point::control1]};
|
||||
auto const grade{control.y / std::hypot(control.x, control.z)};
|
||||
WriteLog(format("SELFTEST drivetilt %s: angles %.3f %.3f %.3f, +x rises %.4f (cone %.4f), +z rises %.4f (grade %.4f), origin %.3f %.3f %.3f, points %.3f", name.c_str(), model->Angles().x, model->Angles().y, model->Angles().z, across.y, cone, forward.y, grade,
|
||||
model->location().x, model->location().y, model->location().z, path.points[segment_data::point::start].y));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switchplace" || command == "switchcurve" || command == "slipplace")
|
||||
{
|
||||
std::string label;
|
||||
glm::dvec3 point{0.0};
|
||||
int direction{1};
|
||||
int side{1};
|
||||
words >> label >> point.x >> point.z >> direction >> side;
|
||||
std::replace(label.begin(), label.end(), '_', ' ');
|
||||
auto &tool{m_switch};
|
||||
tool.templates = editor_track::standard_switch_templates();
|
||||
tool.collected = true;
|
||||
tool.armed = -1;
|
||||
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
||||
if (tool.templates[i].label.rfind(label, 0) == 0)
|
||||
{
|
||||
tool.armed = i;
|
||||
break;
|
||||
}
|
||||
TTrack *nearest{nullptr};
|
||||
double distance{1e9};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
|
||||
continue;
|
||||
auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)};
|
||||
if (d < distance)
|
||||
{
|
||||
distance = d;
|
||||
nearest = track;
|
||||
}
|
||||
}
|
||||
if (tool.armed < 0 || nearest == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST " + command + ": no template " + label + " or no path");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
tool.status.clear();
|
||||
if (command == "switchcurve")
|
||||
{
|
||||
editor_track::curve curve;
|
||||
editor_track::chain chain;
|
||||
std::string error;
|
||||
if (false == editor_track::find_curve(*nearest, m_straights.tolerance, m_route.design.norms.gauge, curve) || false == editor_track::find_chain(curve.from, curve.to, chain, error, true))
|
||||
{
|
||||
WriteLog("SELFTEST switchcurve: no curve");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
tool.frame = sample_chain(chain);
|
||||
tool.frame_tracks = chain.tracks;
|
||||
tool.curved = true;
|
||||
tool.along = nearest_station(tool.frame, point);
|
||||
insert_curved_switch(direction, side);
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const line{editor_track::find_straight(*nearest, m_straights.tolerance)};
|
||||
if (command == "slipplace")
|
||||
{
|
||||
WriteLog(format("SELFTEST slipplace: straight from %s, %zu paths, start %.3f %.3f, %.3f m", nearest->name().c_str(), line.tracks.size(), line.start.x, line.start.z, line.length));
|
||||
insert_double_slip(line, point, tool.templates[tool.armed]);
|
||||
}
|
||||
else
|
||||
insert_switch(line, std::clamp(line.along(point), 0.0, line.length), direction, side);
|
||||
}
|
||||
WriteLog("SELFTEST " + command + ": " + tool.status + " " + m_straights.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switchlink" || command == "branchlink")
|
||||
{
|
||||
std::string label;
|
||||
glm::dvec3 point{0.0};
|
||||
glm::dvec3 target{0.0};
|
||||
if (command == "switchlink")
|
||||
words >> label;
|
||||
words >> point.x >> point.z >> target.x >> target.z;
|
||||
std::replace(label.begin(), label.end(), '_', ' ');
|
||||
auto const log_link = [&](editor_track::branch_link const &Link, std::string const &Reason) {
|
||||
WriteLog(format("SELFTEST %s plan: valid %d, R %.1f m, before %.3f m, after %.3f m, trim %.3f m, shortfall %.3f m, %zu pieces%s", command.c_str(), Link.valid ? 1 : 0, Link.radius, Link.before, Link.after, Link.trim,
|
||||
Link.shortfall, Link.pieces.size(), Reason.empty() ? "" : (", " + Reason).c_str()));
|
||||
for (auto const &piece : Link.pieces)
|
||||
{
|
||||
auto const &a{piece.points[segment_data::point::start]};
|
||||
auto const &b{piece.points[segment_data::point::end]};
|
||||
WriteLog(format("SELFTEST %s piece: %.3f %.3f %.3f -> %.3f %.3f %.3f, L %.3f m, R %.1f m", command.c_str(), a.x, a.y, a.z, b.x, b.y, b.z, geometry::bezier{piece}.plan_length(), static_cast<double>(piece.radius)));
|
||||
}
|
||||
};
|
||||
if (command == "switchlink")
|
||||
{
|
||||
show_track_tab(track_tab::turnout);
|
||||
auto &tool{m_switch};
|
||||
tool.templates = editor_track::standard_switch_templates();
|
||||
tool.collected = true;
|
||||
tool.armed = -1;
|
||||
tool.connection = -1;
|
||||
for (int i = 0; i < static_cast<int>(tool.templates.size()); ++i)
|
||||
if (tool.templates[i].label.rfind(label, 0) == 0)
|
||||
{
|
||||
tool.armed = i;
|
||||
break;
|
||||
}
|
||||
TTrack *nearest{nullptr};
|
||||
double distance{1e9};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
|
||||
continue;
|
||||
auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)};
|
||||
if (d < distance)
|
||||
{
|
||||
distance = d;
|
||||
nearest = track;
|
||||
}
|
||||
}
|
||||
if (tool.armed < 0 || nearest == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST switchlink: no template " + label + " or no path");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
m_node = nearest;
|
||||
ui()->set_node(m_node);
|
||||
m_cursor_override = point;
|
||||
if (false == start_switch_placement())
|
||||
{
|
||||
m_cursor_override.reset();
|
||||
WriteLog("SELFTEST switchlink: not started, " + tool.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
tool.mouse = glm::dvec3{target.x, tool.point.y, target.z};
|
||||
switch_link plan;
|
||||
plan_switch_link(plan);
|
||||
log_link(plan.link, plan.reason);
|
||||
finish_switch_placement();
|
||||
m_cursor_override.reset();
|
||||
WriteLog("SELFTEST switchlink: " + tool.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
TTrack *own{nullptr};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
if (track != nullptr && false == track->m_editorremoved && track->eType == tt_Switch && track->m_paths.size() == 2 && geometry::plan_distance(track->m_paths[1].points[segment_data::point::end], point) < 3.0)
|
||||
own = track;
|
||||
if (own == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST branchlink: no switch branch end there");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
editor_track::snap_target end;
|
||||
end.track = own;
|
||||
end.point = {1, editor_track::point_kind::end};
|
||||
end.position = own->m_paths[1].points[segment_data::point::end];
|
||||
if (false == start_extend(end))
|
||||
{
|
||||
WriteLog("SELFTEST branchlink: the branch end isn't free");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
m_extend.pressed = {-10000.f, -10000.f};
|
||||
m_extend.mouse = glm::dvec3{target.x, m_extend.point.y, target.z};
|
||||
m_extend.snap = editor_track::find_free_end(own, own->iCategoryFlag & 15, m_extend.mouse, 3.0, {own});
|
||||
editor_track::branch_link link;
|
||||
std::string reason;
|
||||
plan_branch_link(link, reason);
|
||||
log_link(link, reason);
|
||||
finish_extend();
|
||||
WriteLog(format("SELFTEST branchlink: switch now %.3f %.3f -> %.3f %.3f", own->m_paths[0].points[segment_data::point::start].x, own->m_paths[0].points[segment_data::point::start].z, own->m_paths[0].points[segment_data::point::end].x,
|
||||
own->m_paths[0].points[segment_data::point::end].z));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switchslide" || command == "switchreplace")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
if (track == nullptr || track->eType != tt_Switch || track->m_paths.size() < 2)
|
||||
{
|
||||
WriteLog("SELFTEST " + command + ": no switch " + name);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switchslide")
|
||||
{
|
||||
double distance{0.0};
|
||||
words >> distance;
|
||||
double low{0.0};
|
||||
double high{0.0};
|
||||
std::string reason;
|
||||
auto const set{double_of(*track)};
|
||||
double length{0.0};
|
||||
auto const room{set.has_value() ? double_room(*set, low, high, length, reason) : switch_room(*track, low, high, reason)};
|
||||
auto const done{slide_switch(*track, distance)};
|
||||
WriteLog(format("SELFTEST switchslide %s %.3f: room %d %.3f .. %.3f, done %d, %s", name.c_str(), distance, room ? 1 : 0, low, high, done ? 1 : 0, m_switch.status.c_str()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
std::string label;
|
||||
int side{1};
|
||||
words >> label >> side;
|
||||
std::replace(label.begin(), label.end(), '_', ' ');
|
||||
auto const templates{editor_track::standard_switch_templates()};
|
||||
auto const found{std::find_if(templates.begin(), templates.end(), [&](editor_track::switch_template const &Template) { return Template.label.rfind(label, 0) == 0; })};
|
||||
if (found == templates.end())
|
||||
{
|
||||
WriteLog("SELFTEST switchreplace: no template " + label);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const set{double_of(*track)};
|
||||
if (set.has_value())
|
||||
track = set->first;
|
||||
auto const &main{track->m_paths[0]};
|
||||
auto const origin{main.points[segment_data::point::start]};
|
||||
auto const chord{geometry::plan_of(main.points[segment_data::point::end] - origin)};
|
||||
auto const length{glm::length(chord)};
|
||||
auto paths{editor_track::place_switch(*found, origin, chord / length, side, (main.points[segment_data::point::end].y - origin.y) / length)};
|
||||
std::string status;
|
||||
auto const done{set.has_value() ? found->twofold() && paths.size() >= 5 && move_double_paths(*set, paths, status) : paths.size() >= 2 && move_switch_paths(*track, paths, status)};
|
||||
if (done && set.has_value())
|
||||
{
|
||||
record_double(*set->first, *set->second, *found);
|
||||
switch_store::follow();
|
||||
records_to_history();
|
||||
straight_refresh();
|
||||
}
|
||||
else if (done)
|
||||
{
|
||||
record_switch(*track, *found);
|
||||
records_to_history();
|
||||
straight_refresh();
|
||||
}
|
||||
WriteLog(format("SELFTEST switchreplace %s %s: done %d, %s", name.c_str(), found->label.c_str(), done ? 1 : 0, status.c_str()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switchpieces" || command == "switchbreak")
|
||||
{
|
||||
double radius{0.0};
|
||||
double ratio{0.0};
|
||||
std::string name;
|
||||
if (command == "switchpieces")
|
||||
words >> radius >> ratio;
|
||||
words >> name;
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->m_paths.size() < 2 || track->SwitchExtension == nullptr)
|
||||
continue;
|
||||
if (false == name.empty() ? track->name() != name : track->m_paths[1].pieces.empty())
|
||||
continue;
|
||||
if (command == "switchbreak")
|
||||
{
|
||||
auto &pieces{track->m_paths[1].pieces};
|
||||
if (pieces.size() < 2)
|
||||
continue;
|
||||
pieces[0].points[segment_data::point::end].x += 0.3;
|
||||
editor_track::commit({track});
|
||||
WriteLog(format("SELFTEST switchbreak %s: path 2 %.3f m, %zu pieces in use, %zu kept", track->name().c_str(), track->SwitchExtension->Segments[1]->GetLength(), track->SwitchExtension->Segments[1]->PieceCount(), pieces.size()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const &segment{*track->SwitchExtension->Segments[1]};
|
||||
auto const &main{track->m_paths[0]};
|
||||
auto const &branch{track->m_paths[1]};
|
||||
auto const origin{segment.FastGetPoint(0.0)};
|
||||
auto const plan{glm::normalize(geometry::plan_of(main.points[segment_data::point::end] - main.points[segment_data::point::start]))};
|
||||
glm::dvec3 const along{plan.x, 0.0, plan.y};
|
||||
auto const reach{branch.points[segment_data::point::end] - branch.points[segment_data::point::start]};
|
||||
auto const hand{along.x * reach.z - along.z * reach.x > 0.0 ? 1.0 : -1.0};
|
||||
auto const normal{glm::dvec3{-along.z, 0.0, along.x} * hand};
|
||||
auto const centre{origin + normal * radius};
|
||||
auto const angle{std::atan(1.0 / ratio)};
|
||||
auto const arc{radius * angle};
|
||||
auto const heading0{-std::atan2(along.x, along.z)};
|
||||
glm::dvec3 position;
|
||||
glm::vec3 rotation;
|
||||
double worst{0.0};
|
||||
for (int i = 0; i <= 10; ++i)
|
||||
{
|
||||
segment.RaPositionGet(arc * i / 10.0, position, rotation);
|
||||
worst = std::max(worst, std::abs(geometry::plan_distance(position, centre) - radius));
|
||||
}
|
||||
segment.RaPositionGet(arc, position, rotation);
|
||||
auto const expected{centre - normal * radius * std::cos(angle) + along * radius * std::sin(angle)};
|
||||
auto const turn{std::abs(std::remainder(rotation.z - heading0, glm::two_pi<double>()))};
|
||||
double low{1e9};
|
||||
double high{-1e9};
|
||||
for (int i = 0; i <= 10; ++i)
|
||||
{
|
||||
segment.RaPositionGet(arc + 0.05 + (segment.GetLength() - arc - 0.1) * i / 10.0, position, rotation);
|
||||
low = std::min(low, static_cast<double>(rotation.z));
|
||||
high = std::max(high, static_cast<double>(rotation.z));
|
||||
}
|
||||
segment.RaPositionGet(arc, position, rotation);
|
||||
WriteLog(format("SELFTEST switchpieces %s: path 2 %.3f m in %zu pieces (%zu kept), arc %.3f m, arc end off %.4f m, turn there %.5f (angle %.5f), axis off the arc %.4f m, straight heading spread %.6f, end %.3f %.3f", track->name().c_str(),
|
||||
segment.GetLength(), segment.PieceCount(), branch.pieces.size(), arc, geometry::plan_distance(position, expected), turn, angle, worst, high - low, segment.FastGetPoint(1.0).x, segment.FastGetPoint(1.0).z));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switchlinks")
|
||||
{
|
||||
auto const name_of = [](TTrack const *Track) { return Track == nullptr ? std::string{"-"} : Track->name().empty() ? std::string{"(noname)"} : Track->name(); };
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->SwitchExtension == nullptr)
|
||||
continue;
|
||||
auto const &extension{*track->SwitchExtension};
|
||||
WriteLog(format("SELFTEST switchlinks %s: prev %s / %s, next %s / %s, lengths %.3f / %.3f m", track->name().c_str(), name_of(extension.pPrevs[0]).c_str(), name_of(extension.pPrevs[1]).c_str(), name_of(extension.pNexts[0]).c_str(), name_of(extension.pNexts[1]).c_str(),
|
||||
extension.Segments[0]->GetLength(), extension.Segments[1]->GetLength()));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switchtemplates")
|
||||
{
|
||||
for (auto const &entry : editor_track::standard_switch_templates())
|
||||
{
|
||||
if (entry.double_slip)
|
||||
continue;
|
||||
auto const paths{editor_track::place_switch(entry, glm::dvec3{0.0}, glm::dvec2{0.0, 1.0}, 1, 0.0)};
|
||||
double gap{0.0};
|
||||
double kink{0.0};
|
||||
std::size_t pieces{0};
|
||||
for (auto const &path : paths)
|
||||
{
|
||||
pieces += path.pieces.size();
|
||||
for (std::size_t i = 0; i + 1 < path.pieces.size(); ++i)
|
||||
{
|
||||
auto const &one{path.pieces[i]};
|
||||
auto const &two{path.pieces[i + 1]};
|
||||
gap = std::max(gap, glm::distance(one.points[segment_data::point::end], two.points[segment_data::point::start]));
|
||||
geometry::bezier const a{one};
|
||||
geometry::bezier const b{two};
|
||||
auto const ta{glm::normalize(geometry::plan_of(a.first(1.0)))};
|
||||
auto const tb{glm::normalize(geometry::plan_of(b.first(0.0)))};
|
||||
kink = std::max(kink, std::abs(geometry::signed_angle(ta, tb)));
|
||||
}
|
||||
if (false == path.pieces.empty())
|
||||
gap = std::max({gap, glm::distance(path.pieces.front().points[segment_data::point::start], path.points[segment_data::point::start]), glm::distance(path.pieces.back().points[segment_data::point::end], path.points[segment_data::point::end])});
|
||||
}
|
||||
std::string ends;
|
||||
for (auto const &path : paths)
|
||||
ends += format(" (%.3f %.3f)", path.points[segment_data::point::end].x, path.points[segment_data::point::end].z);
|
||||
std::string starts;
|
||||
for (std::size_t i = 1; i < paths.size() && i <= 3; i += 2)
|
||||
{
|
||||
geometry::bezier const first{paths[i].pieces.empty() ? paths[i] : paths[i].pieces.front()};
|
||||
auto const tangent{glm::normalize(geometry::plan_of(first.first(0.0)))};
|
||||
starts += format(" %.4f", glm::degrees(std::abs(geometry::signed_angle(glm::dvec2{0.0, 1.0}, tangent))));
|
||||
}
|
||||
WriteLog(format("SELFTEST switchtemplate %s: %s%s, L %.3f 1:%.3f R %.1f a %.3f b %.3f stagger %.3f, %zu paths, %zu pieces, gap %.6f m, kink %.6f deg, start deg%s, ends%s", entry.label.c_str(), entry.type.c_str(), entry.own ? " own" : "", entry.length, entry.ratio, entry.radius, entry.a, entry.b, entry.stagger, paths.size(), pieces, gap,
|
||||
glm::degrees(kink), starts.c_str(), ends.c_str()));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "switches")
|
||||
{
|
||||
for (auto const &entry : switch_store::records())
|
||||
{
|
||||
std::string parts;
|
||||
for (auto const &part : entry.parts)
|
||||
parts += part + " ";
|
||||
WriteLog(format("SELFTEST switch record %s %s: %s, R %.3f 1:%.3f hand %c a %.3f b %.3f main R %.3f, branch R %.3f, km %s, number %s, %s, parts %s", entry.kind.c_str(), entry.name.c_str(), switch_store::label(entry).c_str(), entry.radius, entry.ratio, entry.hand, entry.a,
|
||||
entry.b, entry.main_radius, switch_store::branch_radius(entry), entry.km.has_value() ? format("%.3f", *entry.km / 1000.0).c_str() : "-", entry.number.c_str(), entry.source.c_str(), parts.c_str()));
|
||||
}
|
||||
speed_check::options options;
|
||||
options.norms = m_route.design.norms;
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Switch || track->m_paths.size() < 2)
|
||||
continue;
|
||||
auto const known{switch_store::of(*track)};
|
||||
geometry::bezier const branch{track->m_paths[1]};
|
||||
auto const first{branch.first(0.3)};
|
||||
auto const second{branch.second(0.3)};
|
||||
auto const planar{std::abs(first.x * second.z - first.z * second.x) / std::pow(first.x * first.x + first.z * first.z, 1.5)};
|
||||
auto const verdict{speed_check::check(*track, 1, options)};
|
||||
WriteLog(format("SELFTEST switch %s: %s, branch R at 0.3 %.3f, path R %.3f, branch speed %.1f km/h at R %.1f", track->name().c_str(), known.has_value() ? switch_store::label(*known).c_str() : "no record", planar > 1e-9 ? 1.0 / planar : 0.0,
|
||||
track->m_paths[1].radius, verdict.safe, verdict.radius));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
return selftest_result::unhandled;
|
||||
}
|
||||
282
application/editormodeselftestterrain.cpp
Normal file
282
application/editormodeselftestterrain.cpp
Normal file
@@ -0,0 +1,282 @@
|
||||
/*
|
||||
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/editorSettings.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "application/application.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Event.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Sweep.h"
|
||||
#include "input/keyboardinput.h"
|
||||
#include "launcher/textures_scanner.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <algorithm>
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "scene/sceneterrain.h"
|
||||
#include "editor/editorSpeed.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include "application/editormodeselftest.h"
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_terrain(std::string const &command, std::istringstream &words)
|
||||
{
|
||||
if (command == "orthophoto")
|
||||
{
|
||||
auto config{m_orthophoto.settings()};
|
||||
int fit{1};
|
||||
int inscene{0};
|
||||
words >> config.north >> config.east >> config.radius >> fit >> inscene;
|
||||
config.drape = fit != 0;
|
||||
config.in_scene = inscene != 0;
|
||||
m_orthophoto.settings(config);
|
||||
m_orthophoto.enabled(true);
|
||||
WriteLog(format("SELFTEST orthophoto at %.0f %.0f, radius %d, %s%s", config.north, config.east, config.radius, config.drape ? "fitted" : "flat", config.in_scene ? ", in the scene" : ""));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainmode")
|
||||
{
|
||||
m_terrain_open = true;
|
||||
WriteLog("SELFTEST terrainmode");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainsculpt" || command == "terrainpaint" || command == "terrainlevel")
|
||||
{
|
||||
double x{0.0};
|
||||
double z{0.0};
|
||||
double radius{10.0};
|
||||
double value{1.0};
|
||||
int mode{0};
|
||||
words >> x >> z >> radius >> value >> mode;
|
||||
auto const changed{command == "terrainsculpt" ? m_streamer.sculpt(x, z, radius, value)
|
||||
: command == "terrainlevel" ? m_streamer.level(x, z, radius, value, 1.0, mode)
|
||||
: m_streamer.paint(x, z, radius, 1.0, static_cast<std::uint16_t>(value))};
|
||||
forget_ground();
|
||||
WriteLog(format("SELFTEST %s %.1f %.1f: %s", command.c_str(), x, z, changed ? "changed" : "nothing changed"));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainmodifier")
|
||||
{
|
||||
heightmap::modifier modifier;
|
||||
heightmap::corridor_point a, b;
|
||||
double formation{0.0};
|
||||
double halfwidth{3.5};
|
||||
words >> modifier.name >> a.position.x >> a.position.y >> b.position.x >> b.position.y >> formation >> halfwidth;
|
||||
b.formation = formation;
|
||||
a.formation = b.formation;
|
||||
b.half_width = halfwidth;
|
||||
a.half_width = b.half_width;
|
||||
modifier.points = {a, b};
|
||||
m_streamer.modifier(std::move(modifier));
|
||||
forget_ground();
|
||||
WriteLog(format("SELFTEST terrainmodifier: %zu modifiers", m_streamer.modifiers().size()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainfill")
|
||||
{
|
||||
int layer{0};
|
||||
words >> layer >> m_paint_feather >> m_paint_rotation;
|
||||
m_terrain_layer = layer;
|
||||
m_paint_outline.clear();
|
||||
double x;
|
||||
double z;
|
||||
while (words >> x >> z)
|
||||
m_paint_outline.emplace_back(x, 0.0, z);
|
||||
fill_terrain_area();
|
||||
std::string palette;
|
||||
for (auto const &entry : m_streamer.manifest().layers)
|
||||
palette += format(" [%s %.1f m %.0f deg]", entry.material.c_str(), entry.size, entry.rotation);
|
||||
WriteLog("SELFTEST terrainfill: " + m_terrain_status + ", palette" + palette);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainturn")
|
||||
{
|
||||
int layer{0};
|
||||
float degrees{0.f};
|
||||
words >> layer >> degrees;
|
||||
m_streamer.layer_rotation(static_cast<std::uint16_t>(layer), degrees);
|
||||
WriteLog(format("SELFTEST terrainturn %d: %.1f deg", layer, m_streamer.manifest().layers.at(layer).rotation));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terraintouchup")
|
||||
{
|
||||
int state{1};
|
||||
words >> state;
|
||||
m_streamer.touch_up(state != 0);
|
||||
WriteLog(format("SELFTEST terraintouchup %d", state));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainrestore")
|
||||
{
|
||||
double x{0.0};
|
||||
double z{0.0};
|
||||
double radius{10.0};
|
||||
words >> x >> z >> radius;
|
||||
auto const changed{m_streamer.restore_stitching(x, z, radius, 1.0)};
|
||||
forget_ground();
|
||||
WriteLog(format("SELFTEST terrainrestore %.1f %.1f: %s, touch-ups left %zu", x, z, changed ? "changed" : "nothing changed", m_streamer.touched_up()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainbake")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto const baked{m_streamer.bake_modifier(name)};
|
||||
forget_ground();
|
||||
WriteLog(format("SELFTEST terrainbake %s: %s, %zu modifiers left", name.c_str(), baked ? "baked" : "not found", m_streamer.modifiers().size()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainselect")
|
||||
{
|
||||
int cx{0};
|
||||
int cz{0};
|
||||
std::string add;
|
||||
words >> cx >> cz >> add;
|
||||
if (add != "add")
|
||||
m_terrain_selection.clear();
|
||||
if (m_streamer.exists({cx, cz}))
|
||||
m_terrain_selection.insert({cx, cz});
|
||||
WriteLog(format("SELFTEST terrainselect: %zu selected", m_terrain_selection.size()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainresolution")
|
||||
{
|
||||
float spacing{1.f};
|
||||
int paint{1};
|
||||
words >> spacing >> paint;
|
||||
m_terrain_convert_spacing = heightmap::valid_spacing(spacing);
|
||||
m_terrain_convert_paint = paint != 0;
|
||||
convert_selected_chunks();
|
||||
WriteLog("SELFTEST terrainresolution: " + m_terrain_status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainwater" || command == "terrainwaterdefault")
|
||||
{
|
||||
if (command == "terrainwaterdefault")
|
||||
{
|
||||
std::string material;
|
||||
words >> material;
|
||||
m_streamer.water_material(material);
|
||||
}
|
||||
else
|
||||
{
|
||||
std::size_t index{0};
|
||||
std::string material;
|
||||
float size{0.f};
|
||||
words >> index >> material >> size;
|
||||
auto water{m_streamer.water()};
|
||||
if (index < water.size())
|
||||
{
|
||||
water[index].material = material == "-" ? std::string{} : material;
|
||||
water[index].size = size;
|
||||
m_streamer.water(std::move(water));
|
||||
}
|
||||
}
|
||||
std::string list;
|
||||
for (auto const &body : m_streamer.water())
|
||||
list += format(" [%s: %s, %.1f m]", body.name.c_str(), body.material.empty() ? "default" : body.material.c_str(), body.size);
|
||||
WriteLog("SELFTEST " + command + ": default " + m_streamer.manifest().water_material + list);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainadd")
|
||||
{
|
||||
int count{1};
|
||||
float spacing{m_terrain_spacing};
|
||||
words >> count >> spacing;
|
||||
m_terrain_spacing = heightmap::valid_spacing(spacing);
|
||||
add_chunks_around(std::clamp(count, 1, 8));
|
||||
WriteLog("SELFTEST terrainadd: " + m_terrain_status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainprobe")
|
||||
{
|
||||
double x{0.0};
|
||||
double z{0.0};
|
||||
double height{0.0};
|
||||
words >> x >> z;
|
||||
auto const *chunk{m_streamer.terrain_at(x, z)};
|
||||
std::string materials;
|
||||
if (chunk != nullptr)
|
||||
for (std::size_t slot = 0; slot < std::max<std::size_t>(1, chunk->data().layer_count()); ++slot)
|
||||
{
|
||||
auto const layer{chunk->data().layers[slot]};
|
||||
auto const &palette{m_streamer.manifest().layers};
|
||||
materials += (layer < palette.size() ? palette[layer].material : std::string{"?"}) + " ";
|
||||
}
|
||||
auto const found{m_streamer.height_at(x, z, height)};
|
||||
std::string grid;
|
||||
if (chunk != nullptr)
|
||||
{
|
||||
auto const &data{chunk->data()};
|
||||
double base{0.0};
|
||||
double adjust{0.0};
|
||||
if (false == data.base.empty())
|
||||
base = data.height_in(data.base, x, z);
|
||||
if (false == data.adjust.empty())
|
||||
adjust = data.height_in(data.adjust, x, z);
|
||||
grid = format(", spacing %.1f, paint %d, base %s %.3f, touch-up %.3f", data.spacing(), data.paint, data.base.empty() ? "none" : "", base, adjust);
|
||||
if (data.paint > 0)
|
||||
{
|
||||
// weights at the paint sample nearest the point
|
||||
auto const corner{data.corner()};
|
||||
auto const ix{static_cast<int>(std::lround((x - corner.x) / heightmap::chunk_size * data.paint))};
|
||||
auto const iz{static_cast<int>(std::lround((z - corner.y) / heightmap::chunk_size * data.paint))};
|
||||
grid += ", weights";
|
||||
for (std::size_t slot = 0; slot < data.layer_count(); ++slot)
|
||||
grid += format(" %d", data.weight(slot, std::clamp(ix, 0, data.paint), std::clamp(iz, 0, data.paint)));
|
||||
}
|
||||
}
|
||||
WriteLog(format("SELFTEST terrainprobe %.1f %.1f: %s %.3f, %zu chunks, materials %s%s", x, z, found ? "height" : "no terrain", height, m_streamer.resident(), materials.c_str(), grid.c_str()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "terrainconvert")
|
||||
{
|
||||
load_ground_files();
|
||||
for (auto const &source : m_ground_conversion.files)
|
||||
{
|
||||
glm::dvec3 low{1e30};
|
||||
glm::dvec3 high{-1e30};
|
||||
for (auto index = source.first; index < source.first + source.count; ++index)
|
||||
for (auto const &vertex : m_ground_conversion.triangles[index])
|
||||
{
|
||||
low = glm::min(low, vertex);
|
||||
high = glm::max(high, vertex);
|
||||
}
|
||||
WriteLog("SELFTEST terrain file " + source.file + (source.text ? " (text): " : " (binary): ") + source.message +
|
||||
format(", from %.1f %.1f %.1f to %.1f %.1f %.1f", low.x, low.y, low.z, high.x, high.y, high.z));
|
||||
}
|
||||
m_ground_conversion.status = convert_ground();
|
||||
WriteLog("SELFTEST terrainconvert: " + m_ground_conversion.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
return selftest_result::unhandled;
|
||||
}
|
||||
558
application/editormodeselftesttrack.cpp
Normal file
558
application/editormodeselftesttrack.cpp
Normal file
@@ -0,0 +1,558 @@
|
||||
/*
|
||||
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/editorSettings.hpp"
|
||||
#include "editor/editorGeometry.hpp"
|
||||
#include "application/application.h"
|
||||
#include "application/editoruilayer.h"
|
||||
#include "application/editorprojection.h"
|
||||
|
||||
#include "simulation/simulation.h"
|
||||
#include "simulation/simulationtime.h"
|
||||
#include "scene/scenelayers.h"
|
||||
#include "utilities/Globals.h"
|
||||
#include "utilities/Logs.h"
|
||||
#include "world/Track.h"
|
||||
#include "world/TrackRails.h"
|
||||
#include "world/MemCell.h"
|
||||
#include "world/Event.h"
|
||||
#include "vehicle/DynObj.h"
|
||||
#include "model/AnimModel.h"
|
||||
#include "model/Model3d.h"
|
||||
#include "rendering/renderer.h"
|
||||
#include "world/Sweep.h"
|
||||
#include "input/keyboardinput.h"
|
||||
#include "launcher/textures_scanner.h"
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <algorithm>
|
||||
#include "editor/editorFormat.hpp"
|
||||
#include "editor/editorAlignmentStore.hpp"
|
||||
#include "editor/editorSwitchStore.hpp"
|
||||
#include "scene/sceneterrain.h"
|
||||
#include "editor/editorSpeed.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <fstream>
|
||||
#include <map>
|
||||
#include <sstream>
|
||||
#include "application/editormodeselftest.h"
|
||||
|
||||
editor_mode::selftest_result editor_mode::selftest_track(std::string const &command, std::istringstream &words)
|
||||
{
|
||||
if (command == "probe")
|
||||
{
|
||||
// edge of the last laid model as it was made: its distance from the axis and the height of its top over the rail head
|
||||
std::string name;
|
||||
double step{20.0};
|
||||
words >> name >> step;
|
||||
if (name == "-")
|
||||
name.clear();
|
||||
auto *sweep{name.empty() ? m_sweep.edited : simulation::Sweeps.find(name)};
|
||||
if (sweep == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST probe: nothing laid");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const &definition{sweep->definition()};
|
||||
double const sign{definition.lateral >= 0.0 ? 1.0 : -1.0};
|
||||
struct bin
|
||||
{
|
||||
double edge{std::numeric_limits<double>::max()};
|
||||
std::vector<std::pair<double, double>> points; // lateral, height over the axis
|
||||
};
|
||||
std::map<int, bin> bins;
|
||||
for (auto const &shape : sweep->create_shapes())
|
||||
for (auto const &vertex : shape.data().vertices)
|
||||
{
|
||||
double lateral;
|
||||
auto const station{sweep->project(vertex.position, lateral)};
|
||||
if (lateral * sign <= 0.0)
|
||||
continue;
|
||||
auto const axis{sweep->frame_at(station)};
|
||||
auto &entry{bins[static_cast<int>(std::floor(station / step))]};
|
||||
entry.edge = std::min(entry.edge, std::abs(lateral));
|
||||
entry.points.emplace_back(std::abs(lateral), vertex.position.y - axis.position.y);
|
||||
}
|
||||
WriteLog(format("SELFTEST probe %s: %zu stretches with the model, %.1f - %.1f m of %.1f m", sweep->name().c_str(), bins.size(), sweep->start(), sweep->end(), sweep->length()));
|
||||
for (auto const &[index, entry] : bins)
|
||||
{
|
||||
auto const station{(index + 0.5) * step};
|
||||
if (station < sweep->start() || station > sweep->end())
|
||||
continue;
|
||||
double top{-1000.0};
|
||||
for (auto const &point : entry.points)
|
||||
if (point.first < entry.edge + 0.15)
|
||||
top = std::max(top, point.second);
|
||||
auto const at{sweep->frame_at(station)};
|
||||
auto const head{0.18 + sweep_node::rail_head(at.roll, definition.lateral)};
|
||||
WriteLog(format("SELFTEST probe %s at %.0f m: edge %.3f m from the axis, its top %.3f m over the rail head (cant %.1f deg, gauge widening %.3f m, cant part %.3f m)", sweep->name().c_str(), station, entry.edge, top - head, at.roll,
|
||||
at.widening[definition.lateral >= 0.0 ? 1 : 0], std::max(0.0, at.cant[definition.lateral >= 0.0 ? 1 : 0])));
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "profile")
|
||||
{
|
||||
std::string name;
|
||||
double station{0.0};
|
||||
words >> name >> station;
|
||||
auto *sweep{simulation::Sweeps.find(name)};
|
||||
if (sweep == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST profile: no such sweep");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const &definition{sweep->definition()};
|
||||
std::map<int, std::pair<double, double>> columns;
|
||||
for (auto const &shape : sweep->create_shapes())
|
||||
for (auto const &vertex : shape.data().vertices)
|
||||
{
|
||||
double lateral;
|
||||
auto const at{sweep->project(vertex.position, lateral)};
|
||||
if (std::abs(at - station) > 1.0)
|
||||
continue;
|
||||
auto const axis{sweep->frame_at(at)};
|
||||
auto const height{vertex.position.y - axis.position.y - 0.18 - sweep_node::rail_head(axis.roll, definition.lateral)};
|
||||
auto &column{columns.try_emplace(static_cast<int>(std::floor(lateral / 0.1)), std::numeric_limits<double>::max(), -std::numeric_limits<double>::max()).first->second};
|
||||
column.first = std::min(column.first, height);
|
||||
column.second = std::max(column.second, height);
|
||||
}
|
||||
std::string text;
|
||||
for (auto const &[index, column] : columns)
|
||||
text += format(" [%.1f %.2f..%.2f]", index * 0.1, column.first, column.second);
|
||||
WriteLog(format("SELFTEST profile %s at %.0f m:", name.c_str(), station) + text);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "tracks")
|
||||
{
|
||||
int tab{0};
|
||||
words >> tab;
|
||||
show_track_tab(track_tab::lineside);
|
||||
m_sweep.expand = tab == 0;
|
||||
m_hekto.expand = tab == 1;
|
||||
m_fouling.expand = tab == 2;
|
||||
m_parallel.expand = tab == 3;
|
||||
m_vehicle.expand = tab == 4;
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "curve")
|
||||
{
|
||||
sweep_curve();
|
||||
for (auto const &piece : m_sweep.curve)
|
||||
{
|
||||
auto const &a{piece.points[segment_data::point::start]};
|
||||
auto const &b{piece.points[segment_data::point::end]};
|
||||
WriteLog(format("SELFTEST curve piece %.1f %.1f -> %.1f %.1f", a.x, a.z, b.x, b.z));
|
||||
}
|
||||
if (false == m_sweep.outline.empty())
|
||||
WriteLog(format("SELFTEST curve outline %zu points, %.1f %.1f -> %.1f %.1f, %.1f m", m_sweep.outline.size(), m_sweep.outline.front().x, m_sweep.outline.front().z, m_sweep.outline.back().x, m_sweep.outline.back().z, m_sweep.curve_length));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "lay")
|
||||
{
|
||||
std::string name;
|
||||
std::string side;
|
||||
words >> name >> side;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
if (track == nullptr || track->m_paths.empty())
|
||||
{
|
||||
WriteLog("SELFTEST lay: no track " + name);
|
||||
return selftest_result::pause;
|
||||
}
|
||||
auto const &path{track->m_paths.front()};
|
||||
auto const atend{side == "end"};
|
||||
editor_track::snap_target start;
|
||||
start.track = track;
|
||||
start.point = {0, atend ? editor_track::point_kind::end : editor_track::point_kind::start};
|
||||
start.position = path.points[atend ? segment_data::point::end : segment_data::point::start];
|
||||
auto const &control{path.points[atend ? segment_data::point::control2 : segment_data::point::control1]};
|
||||
start.direction = glm::normalize(control != glm::dvec3{} ? control : path.points[atend ? segment_data::point::start : segment_data::point::end] - start.position);
|
||||
m_lay.points = {start.position};
|
||||
m_lay.start = start;
|
||||
for (glm::dvec3 point{0.0, start.position.y, 0.0}; words >> point.x >> point.z;)
|
||||
m_lay.points.push_back(point);
|
||||
lay_finish({});
|
||||
WriteLog("SELFTEST lay: " + m_lay.status);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "analyse")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
editor_track::curve curve;
|
||||
if (auto *track{simulation::Paths.find(name)}; track != nullptr && editor_track::find_curve(*track, m_straights.tolerance, m_route.design.norms.gauge, curve))
|
||||
{
|
||||
std::string arcs;
|
||||
for (auto const &arc : curve.arcs)
|
||||
arcs += format(" R%.0f/%.1fdeg/after %.1fm", arc.radius, glm::degrees(arc.turn), arc.transition);
|
||||
WriteLog(format("SELFTEST analyse %s: R %.0f, compound %d, in %.1f m, out %.1f m, turn %.1f deg,", name.c_str(), curve.radius, curve.compound ? 1 : 0, curve.transition_in, curve.transition_out, glm::degrees(curve.turn)) + arcs);
|
||||
}
|
||||
else
|
||||
WriteLog("SELFTEST analyse " + name + ": no curve");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "route")
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
int single{1};
|
||||
words >> point.x >> point.z >> single;
|
||||
TTrack *nearest{nullptr};
|
||||
double distance{1e9};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
|
||||
continue;
|
||||
auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)};
|
||||
if (d < distance)
|
||||
{
|
||||
distance = d;
|
||||
nearest = track;
|
||||
}
|
||||
}
|
||||
m_track_tab = track_tab::route;
|
||||
m_route.single = (single != 0);
|
||||
auto const loaded{nearest != nullptr && route_load(*nearest)};
|
||||
WriteLog(format("SELFTEST route: %s from %s, %zu paths, %.3f m, %zu vertices, %d from the design, km %.3f, departed %d by %.3f m, valid %d", loaded ? "loaded" : "failed", nearest != nullptr ? nearest->name().c_str() : "-",
|
||||
m_route.chain.tracks.size(), m_route.chain.length, m_route.design.vertices.size(), m_route.designed, m_route.origin / 1000.0, m_route.departed, m_route.departure, m_route.result.valid ? 1 : 0));
|
||||
WriteLog("SELFTEST route status: " + m_route.status + " " + m_route.error);
|
||||
for (std::size_t i = 0; i < m_route.design.vertices.size(); ++i)
|
||||
{
|
||||
auto const &v{m_route.design.vertices[i]};
|
||||
auto text{format("SELFTEST route vertex %zu: at %.3f %.3f offset %.3f R %.3f L %.3f / %.3f cant %.1f compound %d share %.4f", i, v.position.x, v.position.y, v.offset, v.radius, v.transition_in, v.transition_out, v.cant, v.compound ? 1 : 0, v.share)};
|
||||
for (auto const &arc : v.arcs)
|
||||
text += format(" [R %.3f L %.3f share %.4f]", arc.radius, arc.transition, arc.share);
|
||||
WriteLog(text);
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "routevertex" || command == "routearc" || command == "routeshare")
|
||||
{
|
||||
int index{0};
|
||||
words >> index;
|
||||
if (index < 0 || index >= static_cast<int>(m_route.design.vertices.size()))
|
||||
{
|
||||
WriteLog("SELFTEST " + command + ": no vertex " + std::to_string(index));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto &vertex{m_route.design.vertices[index]};
|
||||
if (command == "routevertex")
|
||||
words >> vertex.radius >> vertex.transition_in >> vertex.transition_out >> vertex.cant;
|
||||
else if (command == "routeshare")
|
||||
words >> vertex.share;
|
||||
else
|
||||
{
|
||||
alignment::compound_arc arc;
|
||||
words >> arc.radius >> arc.transition >> arc.share;
|
||||
vertex.compound = true;
|
||||
vertex.arcs.push_back(arc);
|
||||
}
|
||||
route_update();
|
||||
std::string errors;
|
||||
for (auto const &error : m_route.result.errors)
|
||||
errors += " " + error;
|
||||
WriteLog(format("SELFTEST %s: valid %d, %zu elements, extension %.3f / %.3f", command.c_str(), m_route.result.valid ? 1 : 0, m_route.result.elements.size(), m_route.result.start_extension, m_route.result.end_extension) + errors);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "routekm")
|
||||
{
|
||||
double km{0.0};
|
||||
words >> km;
|
||||
m_route.origin = km * 1000.0;
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "routeapply" || command == "routetake")
|
||||
{
|
||||
if (command == "routeapply")
|
||||
route_apply();
|
||||
else
|
||||
route_take_stored(m_route.departed);
|
||||
WriteLog("SELFTEST " + command + ": " + m_route.status + " " + m_route.error);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "digitizeall")
|
||||
{
|
||||
digitize_all();
|
||||
WriteLog(format("SELFTEST digitizeall: %zu alignments", alignment_store::records().size()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "alignments")
|
||||
{
|
||||
auto const &records{alignment_store::records()};
|
||||
for (std::size_t r = 0; r < records.size(); ++r)
|
||||
{
|
||||
auto const &entry{records[r]};
|
||||
WriteLog(format("SELFTEST alignment %zu in %s: km %.3f, %zu vertices, %zu elements, consistent %d, length %.3f", r, scene::Layers.layer(entry.layer).name.c_str(), entry.origin / 1000.0, entry.design.vertices.size(), entry.elements.size(), entry.consistent ? 1 : 0, entry.result.length));
|
||||
for (auto const &e : entry.elements)
|
||||
WriteLog(format("SELFTEST element %s vertex %d km %.3f L %.3f R %.3f -> %.3f cant %.1f", e.kind.c_str(), e.vertex, (entry.origin + e.chainage) / 1000.0, e.length, e.radius_start, e.radius_end, e.cant));
|
||||
}
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
|
||||
continue;
|
||||
auto const found{alignment_store::on_track(*track)};
|
||||
std::string text{"SELFTEST path " + track->name() + ": "};
|
||||
if (false == found.has_value())
|
||||
{
|
||||
auto const closest{alignment_store::at(geometry::bezier{track->m_paths.front()}.point(0.5), 1000.0)};
|
||||
text += closest.has_value() ? format("off the alignments, %.3f m from alignment %d", closest->offset, closest->record) : std::string{"off the alignments"};
|
||||
}
|
||||
else
|
||||
{
|
||||
auto const e{alignment_store::describe(records[found->record].result.elements[found->element])};
|
||||
text += format("alignment %d element %d %s R %.3f -> %.3f, offset %.4f", found->record, found->element, e.kind.c_str(), e.radius_start, e.radius_end, found->offset);
|
||||
}
|
||||
editor_track::curve curve;
|
||||
if (editor_track::find_curve(*track, m_straights.tolerance, m_route.design.norms.gauge, curve))
|
||||
text += format(" | curve R %.3f L %.3f / %.3f cant %.1f compound %d arcs %zu %s", curve.radius, curve.transition_in, curve.transition_out, curve.cant, curve.compound ? 1 : 0, curve.arcs.size(), curve.designed.has_value() ? "designed" : "recognized");
|
||||
WriteLog(text);
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "vprofile")
|
||||
{
|
||||
std::string from;
|
||||
std::string to;
|
||||
words >> from >> to;
|
||||
auto *first{simulation::Paths.find(from)};
|
||||
auto *last{simulation::Paths.find(to)};
|
||||
if (first == nullptr || last == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST vprofile: no such paths");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
m_profile.origin = 0.0;
|
||||
profile_open(first, last);
|
||||
WriteLog(format("SELFTEST vprofile: %zu spans, %.3f m, km of the start %.3f", m_profile.route.spans.size(), m_profile.route.length, m_profile.origin / 1000.0));
|
||||
for (auto const &zone : m_profile.context.switches)
|
||||
WriteLog(format("SELFTEST vprofile zone %.3f %.3f %s", zone.from, zone.to, zone.name.c_str()));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "vprofileapply")
|
||||
{
|
||||
// a plain grade line from the height of the start of the open route, applied to it
|
||||
double grade{0.0};
|
||||
words >> grade;
|
||||
auto &state{m_profile};
|
||||
if (state.samples.empty())
|
||||
{
|
||||
WriteLog("SELFTEST vprofileapply: no route open");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
auto const start{state.samples.front().position.y};
|
||||
state.line.points = {{0.0, start, 0.0, false}, {state.route.length, start + grade / 1000.0 * state.route.length, 0.0, false}};
|
||||
state.line.join_start = false;
|
||||
state.line.join_end = false;
|
||||
profile_check();
|
||||
profile_apply();
|
||||
WriteLog("SELFTEST vprofileapply: " + state.status + " " + state.error);
|
||||
for (auto const &span : state.route.spans)
|
||||
{
|
||||
if (span.track->eType != tt_Switch || span.track->SwitchExtension == nullptr)
|
||||
continue;
|
||||
for (std::size_t i = 0; i < span.track->m_paths.size() && i < 2; ++i)
|
||||
{
|
||||
auto const &path{span.track->m_paths[i]};
|
||||
WriteLog(format("SELFTEST switch %s path %zu%s: %zu pieces kept, %zu in use, ends %.3f %.3f", span.track->name().c_str(), i + 1, static_cast<int>(i) == span.path ? " (route)" : "", path.pieces.size(), span.track->SwitchExtension->Segments[i]->PieceCount(), path.points[segment_data::point::start].y, path.points[segment_data::point::end].y));
|
||||
}
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "backwardcurve")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto *track{simulation::Paths.find(name)};
|
||||
editor_track::curve curve;
|
||||
editor_track::chain chain;
|
||||
std::string error;
|
||||
if (track == nullptr || false == editor_track::find_curve(*track, m_straights.tolerance, m_route.design.norms.gauge, curve) || false == editor_track::find_chain(curve.from, curve.to, chain, error, true))
|
||||
{
|
||||
WriteLog("SELFTEST backwardcurve: no curve at " + name);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
std::vector<std::pair<TTrack *, bool>> run;
|
||||
for (std::size_t i = chain.tracks.size(); i > 0; --i)
|
||||
run.emplace_back(chain.tracks[i - 1], false == chain.forward[i - 1]);
|
||||
editor_track::curve backward;
|
||||
editor_track::analyse_curve(run, m_route.design.norms.gauge, backward);
|
||||
auto text{format("SELFTEST backwardcurve: %zu paths, R %.3f L %.3f / %.3f cant %.1f compound %d %s", run.size(), backward.radius, backward.transition_in, backward.transition_out, backward.cant, backward.compound ? 1 : 0, backward.designed.has_value() ? "designed" : "recognized")};
|
||||
for (auto const &arc : backward.arcs)
|
||||
text += format(" [R %.3f turn %.4f L %.3f]", arc.radius, arc.turn, arc.transition);
|
||||
WriteLog(text);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "deletetrack" || command == "deletekind")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
if (command == "deletetrack" && name.empty())
|
||||
{
|
||||
delete_selected_track();
|
||||
WriteLog("SELFTEST deletetrack");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
TTrack *chosen{nullptr};
|
||||
if (command == "deletetrack")
|
||||
chosen = simulation::Paths.find(name);
|
||||
else
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
|
||||
continue;
|
||||
auto const found{alignment_store::on_track(*track)};
|
||||
if (found.has_value() && alignment_store::describe(alignment_store::records()[found->record].result.elements[found->element]).kind == name)
|
||||
{
|
||||
chosen = track;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chosen == nullptr || chosen->m_editorremoved)
|
||||
{
|
||||
WriteLog("SELFTEST " + command + ": no path " + name);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
m_node = chosen;
|
||||
ui()->set_node(m_node);
|
||||
auto const deleted{chosen->name()};
|
||||
delete_selected_track();
|
||||
WriteLog("SELFTEST " + command + ": " + deleted + " deleted");
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "btfcheck")
|
||||
{
|
||||
std::string name;
|
||||
words >> name;
|
||||
auto const textfile{Global.asCurrentSceneryPath + name + ".txtf"};
|
||||
auto const binaryfile{Global.asCurrentSceneryPath + name + ".btf"};
|
||||
auto const written{scene::terrain_file::prepare(textfile, binaryfile) == scene::terrain_file::state::binary};
|
||||
auto const kept{scene::terrain_file::extra(binaryfile)};
|
||||
WriteLog(format("SELFTEST btfcheck: binary %d, %zu bytes of the editor lines kept", written ? 1 : 0, kept.size()));
|
||||
std::istringstream lines{kept};
|
||||
std::string line;
|
||||
while (std::getline(lines, line))
|
||||
WriteLog("SELFTEST btf kept: " + line);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "pathsnear")
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
double radius{100.0};
|
||||
words >> point.x >> point.z >> radius;
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->m_paths.empty())
|
||||
continue;
|
||||
for (std::size_t i = 0; i < track->m_paths.size(); ++i)
|
||||
{
|
||||
auto const &path{track->m_paths[i]};
|
||||
auto const &a{path.points[segment_data::point::start]};
|
||||
auto const &b{path.points[segment_data::point::end]};
|
||||
if (geometry::plan_distance(a, point) > radius && geometry::plan_distance(b, point) > radius)
|
||||
continue;
|
||||
auto const &c1{path.points[segment_data::point::control1]};
|
||||
auto const &c2{path.points[segment_data::point::control2]};
|
||||
WriteLog(format("SELFTEST path %s/%zu %s: %.3f %.3f -> %.3f %.3f, c1 %.3f %.3f, c2 %.3f %.3f, %.3f m", track->name().c_str(), i, track->eType == tt_Switch ? "switch" : "normal", a.x, a.z, b.x, b.z, c1.x, c1.z, c2.x, c2.z, geometry::bezier{path}.plan_length()));
|
||||
}
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "splitkind" || command == "nudgekind")
|
||||
{
|
||||
std::string kind;
|
||||
glm::dvec3 offset{0.0};
|
||||
words >> kind >> offset.x >> offset.z;
|
||||
auto const &records{alignment_store::records()};
|
||||
TTrack *chosen{nullptr};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
|
||||
continue;
|
||||
auto const found{alignment_store::on_track(*track)};
|
||||
if (found.has_value() && alignment_store::describe(records[found->record].result.elements[found->element]).kind == kind)
|
||||
{
|
||||
chosen = track;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (chosen == nullptr)
|
||||
{
|
||||
WriteLog("SELFTEST " + command + ": no path over " + kind);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "splitkind")
|
||||
{
|
||||
auto const before{editor_track::capture(*chosen)};
|
||||
auto *created{editor_track::split_path(*chosen, 0.5)};
|
||||
if (created != nullptr)
|
||||
push_track_snapshot({{chosen, before}}, {created});
|
||||
WriteLog("SELFTEST splitkind: " + chosen->name() + " split, the second half " + (created != nullptr ? created->name() : std::string{"-"}));
|
||||
}
|
||||
else
|
||||
{
|
||||
std::vector<std::pair<TTrack *, editor_track::state>> states{{chosen, editor_track::capture(*chosen)}};
|
||||
std::vector<TTrack *> moved{chosen};
|
||||
auto const &path{chosen->m_paths.front()};
|
||||
for (auto const &end : {path.points[segment_data::point::start], path.points[segment_data::point::end]})
|
||||
for (auto *neighbour : {chosen->trPrev, chosen->trNext})
|
||||
{
|
||||
if (neighbour == nullptr || neighbour->m_paths.empty() || std::find(moved.begin(), moved.end(), neighbour) != moved.end())
|
||||
continue;
|
||||
auto const &other{neighbour->m_paths.front()};
|
||||
for (auto const kind : {editor_track::point_kind::start, editor_track::point_kind::end})
|
||||
if (glm::distance(other.points[kind == editor_track::point_kind::start ? segment_data::point::start : segment_data::point::end], end) < 0.01)
|
||||
{
|
||||
states.emplace_back(neighbour, editor_track::capture(*neighbour));
|
||||
editor_track::move_point(*neighbour, {0, kind}, end + offset);
|
||||
moved.push_back(neighbour);
|
||||
break;
|
||||
}
|
||||
}
|
||||
editor_track::translate(*chosen, offset);
|
||||
editor_track::commit(moved);
|
||||
push_track_snapshot(std::move(states), {});
|
||||
WriteLog("SELFTEST nudgekind: " + chosen->name() + " moved with " + std::to_string(moved.size() - 1) + " joined ends");
|
||||
}
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "digitize")
|
||||
{
|
||||
glm::dvec3 point{0.0};
|
||||
words >> point.x >> point.z;
|
||||
TTrack *nearest{nullptr};
|
||||
double distance{1e9};
|
||||
for (auto *track : simulation::Paths.sequence())
|
||||
{
|
||||
if (track == nullptr || track->m_editorremoved || track->eType != tt_Normal || track->m_paths.empty())
|
||||
continue;
|
||||
auto const d{geometry::plan_distance(editor_track::point_at(*track, editor_track::nearest_parameter(*track, point)), point)};
|
||||
if (d < distance)
|
||||
{
|
||||
distance = d;
|
||||
nearest = track;
|
||||
}
|
||||
}
|
||||
auto const kept{nearest != nullptr && digitize_drawn({nearest}, false)};
|
||||
WriteLog(format("SELFTEST digitize %s: %d", nearest != nullptr ? nearest->name().c_str() : "-", kept ? 1 : 0));
|
||||
return selftest_result::handled;
|
||||
}
|
||||
if (command == "parallel")
|
||||
{
|
||||
words >> m_parallel.spacing >> m_parallel.side;
|
||||
m_parallel.scope = 1;
|
||||
m_parallel.built_for = nullptr;
|
||||
parallel_build();
|
||||
WriteLog("SELFTEST parallel: " + m_parallel.status + " " + m_parallel.error);
|
||||
return selftest_result::handled;
|
||||
}
|
||||
return selftest_result::unhandled;
|
||||
}
|
||||
Reference in New Issue
Block a user