17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 15:54:53 +01:00
Files
maszyna/application/editormodeselftestmonkey.cpp
maj00r bbb3e9e61e feat(editor): laying and editing the track as a beginner meets them: the points clicked and not laid yet named at the top of the panel with Lay (Enter) and Drop the points, Enter shown by the bar at the bottom while there are some, and a first Esc over them only warning, the second one dropping them; the readout by the cursor kept in the 3d view, on the other side of the cursor where the view ends, with only the first warning of a curve and how many more the panel lists; the grid, the labels and the lines drawn over the view no longer seen through the docks; the gizmo in the tracks hidden until Q, W or E or its buttons above the view bring it out, an arrow there hiding it again, so a selected track isn't moved by chance; the radius of the curves at the bends headed so in the panel, the laid line told in metres and pieces, the hints of the select tool and of a straight shifted around an obstacle translated. An empty scenery opens facing north, the top view with the north up as the compass and the orthophoto have it (on LK2 Warszawa Wschodnia left, Rembertow right). The tutorial tells the laying as it works now: the bends clicked and Enter, the curves made at them and orange where too sharp for the speed, Tab for a curve traced, the radius by Ctrl+wheel when joining, the gizmo by Q W E, the terrain model loaded and fitted from the panel of the tracks, signals by I; its texts wrap to a dock it's put in, new clips of laying and of the curves, recorded with the selftest badge drawing the mouse or the key in use; the empty scenery and the shortcuts say the same
2026-10-25 11:02:45 +01:00

363 lines
14 KiB
C++

/*
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/.
*/
// monkey tests: random gestures over the track tools, and the checks of the tracks they're run against
#include "stdafx.h"
#include "application/editormode.h"
#include "application/editoruilayer.h"
#include "application/editorprojection.h"
#include "editor/editorFormat.hpp"
#include "simulation/simulation.h"
#include "utilities/Logs.h"
#include "world/Track.h"
#include "imgui/imgui.h"
#include "imgui/imgui_internal.h"
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <random>
#include <string>
#include <vector>
// monkey <seed> <steps> random gestures of the mouse and the keys over the 3d view, the tools, their options,
// the inspector and the profile, written into the script after this line; the tracks
// are checked every tenth step
// monkeyview <seed> the camera over a track picked at random, looking at it from 30..120 m
// monkeyat <seed> the mouse on a track seen in the view, at an end, the middle or a point between
// invariants logs the tracks whose points, rolls or length aren't finite, or that have no path
// undoall undoes every change in the history
// trackhash logs the number of the tracks and a hash of their names and points to the millimetre
// tutorial <page> opens the tutorial at the lesson, from 0; -1 closes it
// badge [<x> <y> <gesture>] the mouse or the keys drawn with the bottom left corner at the point of the window, as
// "LMB" or "Enter", for the clips of the tutorial; with nothing after it none
// shortcuts [filter] opens the list of the shortcuts
editor_mode::selftest_result editor_mode::selftest_monkey(std::string const &command, std::istringstream &words)
{
if (command == "monkey")
{
unsigned int seed{1};
int steps{100};
words >> seed >> steps;
std::mt19937 random{seed};
auto const pick = [&](int const Count) { return std::uniform_int_distribution<int>(0, Count - 1)(random); };
auto const within = [&](float const Low, float const High) { return std::uniform_real_distribution<float>(Low, std::max(Low + 1.0f, High))(random); };
struct area
{
ImVec2 low, high;
};
std::vector<area> panels;
for (auto const *name : {"##editortoolbar", "###tooloptions", "###inspector", "###profilestrip"})
if (auto const *window{ImGui::FindWindowByName(name)}; window != nullptr && (window->Active || window->WasActive))
panels.push_back({window->Pos, ImVec2(window->Pos.x + window->Size.x, window->Pos.y + window->Size.y)});
auto const view_low{editor_ui::view_min()};
auto const view_high{editor_ui::view_max()};
auto const in_view = [&]() { return ImVec2(within(view_low.x + 8.0f, view_high.x - 8.0f), within(view_low.y + 8.0f, view_high.y - 8.0f)); };
auto const somewhere = [&]() {
if (panels.empty() || pick(3) == 0)
return in_view();
auto const &panel{panels[pick(static_cast<int>(panels.size()))]};
return ImVec2(within(panel.low.x + 4.0f, panel.high.x - 4.0f), within(panel.low.y + 4.0f, panel.high.y - 4.0f));
};
std::vector<TTrack *> tracks;
for (auto *track : simulation::Paths.sequence())
if (track != nullptr && false == track->m_editorremoved && false == track->name().empty())
tracks.push_back(track);
std::vector<std::string> lines;
auto const mouse = [&](ImVec2 const At) {
if (At.x >= view_low.x && At.x <= view_high.x && At.y >= view_low.y && At.y <= view_high.y && pick(2) == 0)
lines.push_back(format("monkeyat %u", static_cast<unsigned int>(random())));
else
lines.push_back(format("mouse %.0f %.0f", At.x, At.y));
};
auto const click = [&](char const *Button) {
lines.push_back(std::string{"button "} + Button + " press");
lines.emplace_back("wait 1");
lines.push_back(std::string{"button "} + Button + " release");
lines.emplace_back("wait 2");
};
char const *const tools[]{"esc", "l", "g", "c", "t", "n", "u", "i", "b", "p", "v", "j", "k", "o", "q", "w", "e"};
char const *const modifiers[]{"", "ctrl", "shift", "alt"};
for (int step = 0; step < steps; ++step)
{
lines.push_back(format("log monkey %u step %d", seed, step));
switch (pick(12))
{
case 0:
case 1:
{
// a click, now and then with a modifier held
auto const modifier{modifiers[pick(6) < 4 ? 0 : 1 + pick(3)]};
mouse(somewhere());
lines.emplace_back("wait 1");
if (*modifier != '\0')
lines.push_back(std::string{"press "} + modifier);
click("left");
if (*modifier != '\0')
lines.push_back(std::string{"release "} + modifier);
break;
}
case 2:
mouse(somewhere());
lines.emplace_back("wait 1");
click("left");
click("left");
break;
case 3:
{
// a drag, in the view or on a panel; now and then with Shift held, a box of tracks
auto const from{pick(3) == 0 ? somewhere() : in_view()};
auto const boxed{pick(4) == 0};
mouse(from);
lines.emplace_back("wait 1");
if (boxed)
lines.emplace_back("press shift");
lines.emplace_back("button left press");
lines.emplace_back("wait 2");
auto const to{ImVec2(from.x + within(-200.0f, 200.0f), from.y + within(-150.0f, 150.0f))};
for (int part = 1; part <= 4; ++part)
{
mouse(ImVec2(from.x + (to.x - from.x) * part / 4.0f, from.y + (to.y - from.y) * part / 4.0f));
lines.emplace_back("wait 1");
}
lines.emplace_back("button left release");
if (boxed)
lines.emplace_back("release shift");
lines.emplace_back("wait 2");
break;
}
case 4:
mouse(in_view());
lines.emplace_back("wait 1");
click("right");
if (pick(2) == 0)
lines.emplace_back("tap esc");
break;
case 5:
{
auto const modifier{modifiers[pick(4)]};
mouse(pick(4) == 0 ? somewhere() : in_view());
lines.emplace_back("wait 1");
if (*modifier != '\0')
lines.push_back(std::string{"press "} + modifier);
lines.push_back(format("wheel %d", pick(2) == 0 ? 1 : -1));
lines.emplace_back("wait 1");
if (*modifier != '\0')
lines.push_back(std::string{"release "} + modifier);
break;
}
case 6:
lines.push_back(std::string{"tap "} + tools[pick(static_cast<int>(std::size(tools)))]);
lines.emplace_back("wait 2");
break;
case 7:
{
char const *const keys[]{"enter", "backspace", "del", "esc", "tab", "space"};
lines.push_back(std::string{"tap "} + keys[pick(static_cast<int>(std::size(keys)))]);
lines.emplace_back("wait 2");
break;
}
case 8:
lines.emplace_back(pick(3) == 0 ? "redo" : "undo");
lines.emplace_back("wait 2");
break;
case 9:
if (false == tracks.empty())
lines.push_back("select " + tracks[pick(static_cast<int>(tracks.size()))]->name());
lines.emplace_back("wait 1");
// now and then the plan of its line, with the band of the curvature in the panel, or the grade line of the
// whole line
if (auto const tool{pick(4)}; tool < 2)
{
lines.emplace_back(tool == 0 ? "tracktab G" : "tracktab P");
lines.emplace_back("wait 2");
}
break;
case 10:
lines.push_back(format("type %d", pick(500) - 50));
lines.emplace_back("wait 1");
break;
default:
// a gesture held a while: the mouse moved with the button down over a few frames, keys tapped meanwhile
mouse(in_view());
lines.emplace_back("wait 1");
lines.emplace_back("button left press");
lines.emplace_back("wait 3");
mouse(in_view());
lines.emplace_back("wait 1");
lines.push_back(std::string{"press "} + (pick(2) == 0 ? "ctrl" : "shift"));
lines.push_back(format("wheel %d", pick(2) == 0 ? 1 : -1));
lines.emplace_back("wait 1");
lines.emplace_back("release ctrl");
lines.emplace_back("release shift");
mouse(in_view());
lines.emplace_back("wait 1");
lines.emplace_back("button left release");
lines.emplace_back("wait 2");
break;
}
if (step % 10 == 9)
lines.emplace_back("invariants");
if (step % 25 == 0)
lines.push_back(format("monkeyview %u", static_cast<unsigned int>(random())));
}
lines.emplace_back("tap esc");
lines.emplace_back("wait 2");
lines.emplace_back("invariants");
WriteLog(format("SELFTEST monkey %u: %d steps, %zu lines, %zu panels", seed, steps, lines.size(), panels.size()));
m_selftest.lines.insert(m_selftest.lines.begin() + static_cast<std::ptrdiff_t>(m_selftest.next), lines.begin(), lines.end());
return selftest_result::handled;
}
if (command == "monkeyview" || command == "monkeyat")
{
unsigned int seed{1};
words >> seed;
std::mt19937 random{seed};
std::vector<TTrack *> tracks;
for (auto *track : simulation::Paths.sequence())
if (track != nullptr && false == track->m_editorremoved && false == track->m_paths.empty())
tracks.push_back(track);
if (tracks.empty())
return selftest_result::handled;
auto const share = [&]() {
double const shares[]{0.0, 0.5, 1.0, 0.25, 0.75, 0.1, 0.9};
return shares[std::uniform_int_distribution<int>(0, 6)(random)];
};
auto const along = [&](TTrack const &Track, double const Share) {
auto const &path{Track.m_paths.front()};
auto const &a{path.points[segment_data::point::start]};
auto const &b{path.points[segment_data::point::end]};
auto const c1{a + path.points[segment_data::point::control1]};
auto const c2{b + path.points[segment_data::point::control2]};
auto const u{1.0 - Share};
return u * u * u * a + 3.0 * u * u * Share * c1 + 3.0 * u * Share * Share * c2 + Share * Share * Share * b;
};
if (command == "monkeyview")
{
auto const &track{*tracks[std::uniform_int_distribution<std::size_t>(0, tracks.size() - 1)(random)]};
auto const target{along(track, 0.5)};
auto const distance{std::uniform_real_distribution<double>(30.0, 120.0)(random)};
auto const angle{std::uniform_real_distribution<double>(0.0, 6.2831853)(random)};
glm::dvec3 const from{target.x + std::cos(angle) * distance * 0.7, target.y + distance * 0.6, target.z + std::sin(angle) * distance * 0.7};
auto const look{glm::normalize(target - 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;
WriteLog("SELFTEST monkeyview " + (track.name().empty() ? std::string{"(noname)"} : track.name()));
return selftest_result::handled;
}
screen_projection const projection;
auto const low{editor_ui::view_min()};
auto const high{editor_ui::view_max()};
std::shuffle(tracks.begin(), tracks.end(), random);
for (auto const *track : tracks)
{
ImVec2 at;
if (false == projection.project(along(*track, share()), at) || at.x < low.x + 4.0f || at.y < low.y + 4.0f || at.x > high.x - 4.0f || at.y > high.y - 4.0f)
continue;
m_selftest.input.mouse_to(glm::dvec2{at.x, at.y});
return selftest_result::handled;
}
m_selftest.input.mouse_to(glm::dvec2{(low.x + high.x) * 0.5f, (low.y + high.y) * 0.5f});
return selftest_result::handled;
}
if (command == "undoall")
{
std::size_t count{0};
while (false == m_history.empty() && count < 10000)
{
auto const before{m_history.size()};
undo_last();
++count;
if (m_history.size() >= before)
break;
}
WriteLog(format("SELFTEST undoall: %zu undone, %zu left", count, m_history.size()));
return selftest_result::handled;
}
if (command == "trackhash")
{
std::vector<std::string> entries;
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved)
continue;
std::string entry{track->name()};
for (auto const &path : track->m_paths)
for (auto const &point : path.points)
entry += format(" %.3f %.3f %.3f", point.x, point.y, point.z);
entries.push_back(std::move(entry));
}
std::sort(entries.begin(), entries.end());
std::uint64_t hash{1469598103934665603ull};
for (auto const &entry : entries)
for (auto const character : entry)
hash = (hash ^ static_cast<unsigned char>(character)) * 1099511628211ull;
WriteLog(format("SELFTEST trackhash %zu tracks %016llx", entries.size(), static_cast<unsigned long long>(hash)));
return selftest_result::handled;
}
if (command == "tutorial")
{
int page{0};
words >> page;
m_first_steps_open = page >= 0;
m_tutorial_page = std::max(0, page);
return selftest_result::handled;
}
if (command == "badge")
{
std::string gesture;
words >> m_selftest.badge_at.x >> m_selftest.badge_at.y;
std::getline(words >> std::ws, gesture);
m_selftest.badge = gesture;
return selftest_result::handled;
}
if (command == "shortcuts")
{
m_shortcuts_open = true;
return selftest_result::handled;
}
if (command == "invariants")
{
std::size_t count{0};
std::vector<std::string> bad;
auto const finite = [](glm::dvec3 const &Point) { return std::isfinite(Point.x) && std::isfinite(Point.y) && std::isfinite(Point.z); };
for (auto *track : simulation::Paths.sequence())
{
if (track == nullptr || track->m_editorremoved)
continue;
++count;
std::string fault;
if (track->m_paths.empty())
fault = "no path";
for (auto const &path : track->m_paths)
{
for (auto const &point : path.points)
if (false == finite(point))
fault = "point not finite";
if (false == std::isfinite(path.rolls[0]) || false == std::isfinite(path.rolls[1]))
fault = "roll not finite";
}
if (false == std::isfinite(track->Length()) || track->Length() < 0.0)
fault = format("length %f", track->Length());
if (false == fault.empty())
bad.push_back((track->name().empty() ? std::string{"(noname)"} : track->name()) + ": " + fault);
}
auto text{format("SELFTEST invariants: %zu tracks, %zu bad, history %zu, %.0f fps", count, bad.size(), m_history.size(), ImGui::GetIO().Framerate)};
for (std::size_t i = 0; i < bad.size() && i < 10; ++i)
text += "\n " + bad[i];
WriteLog(text);
return selftest_result::handled;
}
return selftest_result::unhandled;
}