mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-30 09:40:29 +01:00
Merge the track layout editor into cpp20-modules, ported to modules
The editor branch was written against headers and the PCH, both of which this
branch has done away with, so its own files were ported rather than merged: the
five headers it added are now interface units and their .cpp files module
implementation units.
- editor/{puwg1992,polandmap,turnouts,orthophoto} become eu07.editor.* modules,
application/planpanel becomes eu07.application.planpanel. The plan tool keeps
to its own module rather than joining the eu07.simcore partitions: it stands on
the editor's libraries and eu07.application.uilayer, not on the simulation core,
so :editoruilayer can export import it without a cycle.
- glm arrives by import eu07.glm, never by include, as the rest of the tree does.
maj0sted is header-only and glm-free, so its headers stay includes in the global
module fragment - every unit that names those types includes them itself, since
a global fragment is not visible across units.
- the one textual conflict was :editoruilayer's include block against this
branch's imports; resolved this branch's way plus the planpanel import.
Builds with clang 22 (-DWITH_BETTER_RENDERER=OFF) and starts into the editor on
empty ground with -editor alone.
This commit is contained in:
@@ -269,77 +269,74 @@ void eu07_application::DiscordRPCService()
|
||||
discord_rpc.startTimestamp = static_cast<int64_t>(now_c);
|
||||
discord_rpc.largeImageText = "MaSzyna";
|
||||
|
||||
// run loop
|
||||
while (!glfwWindowShouldClose(m_windows.front()) && !m_modestack.empty() && !Global.applicationQuitOrder)
|
||||
// run until quit — do NOT require a non-empty mode stack: pop/push during scenery reload
|
||||
// briefly empties the stack, and exiting here would Discord_Shutdown() mid-session (or race on top())
|
||||
while (!Global.applicationQuitOrder && !m_windows.empty() && !glfwWindowShouldClose(m_windows.front()))
|
||||
{
|
||||
auto currentMode = m_modestack.top();
|
||||
if (m_modestack.empty())
|
||||
{
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(100));
|
||||
continue;
|
||||
}
|
||||
|
||||
auto const currentMode = m_modestack.top();
|
||||
if (currentMode == mode::launcher)
|
||||
{
|
||||
// in launcher mode
|
||||
|
||||
discord_rpc.state = Translations.lookup_c("In main menu");
|
||||
discord_rpc.details = Translations.lookup_c("Browsing scenarios...");
|
||||
discord_rpc.largeImageKey = "";
|
||||
discord_rpc.largeImageText = "MaSzyna";
|
||||
// RPC upload
|
||||
Discord_UpdatePresence(&discord_rpc);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); // update RPC every 5 secs
|
||||
continue;
|
||||
}
|
||||
else if (currentMode == mode::scenarioloader)
|
||||
{
|
||||
std::string rpcScnName = Global.SceneryFile;
|
||||
if (rpcScnName[0] == '$')
|
||||
if (!rpcScnName.empty() && rpcScnName.front() == '$')
|
||||
rpcScnName.erase(0, 1);
|
||||
rpcScnName.erase(rpcScnName.size() - 4, 4);
|
||||
if (rpcScnName.find('_') != std::string::npos)
|
||||
{
|
||||
std::replace(rpcScnName.begin(), rpcScnName.end(), '_', ' ');
|
||||
}
|
||||
if (rpcScnName.size() >= 4)
|
||||
rpcScnName.erase(rpcScnName.size() - 4, 4);
|
||||
std::replace(rpcScnName.begin(), rpcScnName.end(), '_', ' ');
|
||||
|
||||
// realworld timestamp from datetime
|
||||
static std::string state = Translations.lookup_s("Scenery: ") + rpcScnName;
|
||||
static std::string state;
|
||||
state = Translations.lookup_s("Scenery: ") + rpcScnName;
|
||||
discord_rpc.state = state.c_str();
|
||||
discord_rpc.details = Translations.lookup_c("Loading scenery...");
|
||||
discord_rpc.largeImageKey = "logo";
|
||||
discord_rpc.largeImageText = "MaSzyna";
|
||||
|
||||
// RPC upload
|
||||
Discord_UpdatePresence(&discord_rpc);
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); // update RPC every 5 secs
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentMode != mode::driver)
|
||||
continue;
|
||||
|
||||
// Discord RPC updater
|
||||
if (simulation::is_ready)
|
||||
else if (currentMode == mode::editor)
|
||||
{
|
||||
discord_rpc.state = "Dłubie w edytorze";
|
||||
discord_rpc.details = Global.SceneryFile.c_str();
|
||||
discord_rpc.largeImageKey = "logo";
|
||||
discord_rpc.largeImageText = "MaSzyna";
|
||||
discord_rpc.smallImageKey = "";
|
||||
discord_rpc.smallImageText = "";
|
||||
Discord_UpdatePresence(&discord_rpc);
|
||||
}
|
||||
else if (currentMode == mode::driver && simulation::is_ready)
|
||||
{
|
||||
std::string PlayerVehicle;
|
||||
if (simulation::Train != nullptr)
|
||||
if (simulation::Train != nullptr && simulation::Train->Dynamic() != nullptr)
|
||||
{
|
||||
PlayerVehicle = simulation::Train->name();
|
||||
// make to upper
|
||||
for (auto &c : PlayerVehicle)
|
||||
c = toupper(c);
|
||||
c = static_cast<char>(toupper(static_cast<unsigned char>(c)));
|
||||
|
||||
PlayerVehicle = Translations.lookup_s("Driving: ") + PlayerVehicle;
|
||||
discord_rpc.details = PlayerVehicle.c_str();
|
||||
|
||||
uint16_t playerTrainVelocity = simulation::Train->Dynamic()->GetVelocity();
|
||||
auto const playerTrainVelocity = static_cast<uint16_t>(simulation::Train->Dynamic()->GetVelocity());
|
||||
if (playerTrainVelocity > 1)
|
||||
{
|
||||
// ikonka ze jedziemy i nie spimy
|
||||
discord_rpc.smallImageKey = "driving";
|
||||
std::string smallText = Translations.lookup_s("Speed: ") + std::to_string(playerTrainVelocity) + " km/h";
|
||||
static std::string smallText;
|
||||
smallText = Translations.lookup_s("Speed: ") + std::to_string(playerTrainVelocity) + " km/h";
|
||||
discord_rpc.smallImageText = smallText.c_str();
|
||||
}
|
||||
else
|
||||
{
|
||||
// krecimy postoj
|
||||
discord_rpc.smallImageKey = "halt";
|
||||
discord_rpc.smallImageText = Translations.lookup_c("Stopped");
|
||||
}
|
||||
@@ -347,7 +344,8 @@ void eu07_application::DiscordRPCService()
|
||||
|
||||
Discord_UpdatePresence(&discord_rpc);
|
||||
}
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000)); // update RPC every 5 secs
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(5000));
|
||||
}
|
||||
|
||||
Discord_Shutdown();
|
||||
@@ -1172,6 +1170,7 @@ int eu07_application::init_settings(int Argc, char *Argv[])
|
||||
}
|
||||
|
||||
// process command line arguments
|
||||
auto scenerywasasked = false;
|
||||
for (int i = 1; i < Argc; ++i)
|
||||
{
|
||||
|
||||
@@ -1182,6 +1181,7 @@ int eu07_application::init_settings(int Argc, char *Argv[])
|
||||
if (i + 1 < Argc)
|
||||
{
|
||||
Global.SceneryFile = ToLower(Argv[++i]);
|
||||
scenerywasasked = true;
|
||||
}
|
||||
}
|
||||
else if (token == "-v")
|
||||
@@ -1191,14 +1191,27 @@ int eu07_application::init_settings(int Argc, char *Argv[])
|
||||
Global.local_start_vehicle = ToLower(Argv[++i]);
|
||||
}
|
||||
}
|
||||
else if (token == "-editor")
|
||||
{
|
||||
Global.editor_startup = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "usage: " << std::string(Argv[0]) << " [-s sceneryfilepath]"
|
||||
<< " [-v vehiclename]" << std::endl;
|
||||
<< " [-v vehiclename]"
|
||||
<< " [-editor]" << std::endl;
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (Global.editor_startup && false == scenerywasasked)
|
||||
{
|
||||
// the editor opens on empty ground unless a scenery was actually asked for. whatever the ini
|
||||
// happens to name is the last thing that was driven, which has nothing to do with editing; an
|
||||
// empty scenery still gives the region, the lighting and the clock, just nothing standing on it
|
||||
Global.SceneryFile = "pusta.scn";
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -624,6 +624,20 @@ void editor_mode::redo_last()
|
||||
|
||||
bool editor_mode::update()
|
||||
{
|
||||
if (Global.editor_reset_scenery)
|
||||
{
|
||||
// starting a new map: drop whatever scenery is loaded and come back on empty ground. done
|
||||
// here rather than where it was asked for, because taking the editor off the mode stack in
|
||||
// the middle of drawing its own panels would pull the ground out from under them
|
||||
Global.editor_reset_scenery = false;
|
||||
Global.editor_startup = true; // the editor is the base mode from here on, whatever started it
|
||||
Global.SceneryFile = "pusta.scn";
|
||||
simulation::is_ready = false;
|
||||
Application.pop_mode();
|
||||
Application.push_mode(eu07_application::mode::scenarioloader);
|
||||
return true;
|
||||
}
|
||||
|
||||
Timer::UpdateTimers(true);
|
||||
|
||||
simulation::State.update_clocks();
|
||||
@@ -1322,10 +1336,64 @@ void editor_mode::render_gizmo()
|
||||
}
|
||||
}
|
||||
|
||||
void editor_mode::on_scroll(double const Xoffset, double const Yoffset)
|
||||
{
|
||||
if (false == Global.editor_ortho)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// the wheel decides how much ground fits on screen. shift the camera so the point under the
|
||||
// cursor stays put - same idea as zooming a map, rather than scaling around the view centre
|
||||
auto const oldextent = Global.editor_ortho_extent;
|
||||
auto const newextent = std::clamp(oldextent * std::pow(0.85f, static_cast<float>(Yoffset)), 5.0f, 20000.0f);
|
||||
if (newextent == oldextent)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
auto const width = std::max(1, Global.window_size.x);
|
||||
auto const height = std::max(1, Global.window_size.y);
|
||||
auto const ndcx = (static_cast<double>(Global.cursor_pos.x) / width) * 2.0 - 1.0;
|
||||
auto const ndcy = 1.0 - (static_cast<double>(Global.cursor_pos.y) / height) * 2.0;
|
||||
auto const halfheight = static_cast<double>(oldextent);
|
||||
auto const halfwidth = halfheight * static_cast<double>(width) / static_cast<double>(height);
|
||||
auto const scale = static_cast<double>(newextent) / static_cast<double>(oldextent);
|
||||
|
||||
// top-down with yaw pinned to north: view x follows world x, view y follows -world z
|
||||
Camera.Pos.x += ndcx * halfwidth * (1.0 - scale);
|
||||
Camera.Pos.z -= ndcy * halfheight * (1.0 - scale);
|
||||
|
||||
Global.editor_ortho_extent = newextent;
|
||||
}
|
||||
|
||||
void editor_mode::update_camera(double const Deltatime)
|
||||
{
|
||||
Camera.Update();
|
||||
|
||||
// the plan view looks straight down. the camera keeps its ground position, so panning works just
|
||||
// as it does in the perspective view, but the orientation is pinned and handed back on the way out
|
||||
if (Global.editor_ortho)
|
||||
{
|
||||
if (false == m_orthoactive)
|
||||
{
|
||||
m_orthoangle = Camera.Angle;
|
||||
m_orthoactive = true;
|
||||
}
|
||||
Camera.Angle.x = glm::radians(-90.0f);
|
||||
Camera.Angle.z = 0.0f;
|
||||
// a plan is read with north up. the editor's own camera starts turned around, which left the
|
||||
// whole view - scenery, imagery and drawing alike - standing on its head
|
||||
Camera.Angle.y = 0.0f;
|
||||
// high enough to clear anything the scenery may have under the cursor
|
||||
Camera.Pos.y = std::max(Camera.Pos.y, 500.0);
|
||||
}
|
||||
else if (m_orthoactive)
|
||||
{
|
||||
Camera.Angle = m_orthoangle;
|
||||
m_orthoactive = false;
|
||||
}
|
||||
|
||||
// focus animation runs after Camera.Update() so it overrides any residual velocity/rotation;
|
||||
// it smoothly drives both position and orientation toward the framed object
|
||||
if (m_focus_active)
|
||||
@@ -1362,6 +1430,20 @@ void editor_mode::update_camera(double const Deltatime)
|
||||
|
||||
void editor_mode::enter()
|
||||
{
|
||||
if (Global.editor_startup)
|
||||
{
|
||||
// the scenery camera is set up by the driver mode, which never runs when we start straight in
|
||||
// the editor; without this we'd copy an untouched global camera below and render a black view
|
||||
auto const scenerycamera = Global.FreeCameraInit[0] != glm::dvec3(0.0);
|
||||
// a scenery doesn't have to name a camera; fall back on the editor's own view above the origin
|
||||
Camera.Init(scenerycamera ? Global.FreeCameraInit[0] : glm::dvec3{0.0, 15.0, 0.0},
|
||||
scenerycamera ? Global.FreeCameraInitAngle[0] : glm::vec3{glm::radians(-30.0f), glm::radians(180.0f), 0.0f}, nullptr);
|
||||
Global.pCamera = Camera;
|
||||
Global.pDebugCamera = Camera;
|
||||
FreeFlyModeFlag = true;
|
||||
Timer::ResetTimers(); // keep the scenery loading time out of the first frame's delta
|
||||
}
|
||||
|
||||
m_statebackup = {Global.pCamera, FreeFlyModeFlag, Global.ControlPicking};
|
||||
|
||||
Camera = Global.pCamera;
|
||||
@@ -1377,8 +1459,10 @@ void editor_mode::enter()
|
||||
Camera.m_owner = nullptr;
|
||||
Camera.LookAt = vehicle->GetPosition();
|
||||
Camera.RaLook(); // single camera reposition
|
||||
FreeFlyModeFlag = true;
|
||||
}
|
||||
// the editor always works with a free camera. starting straight in it there's no vehicle to
|
||||
// step out of, but the flag still has to go up or the camera stays in cab mode without a cab
|
||||
FreeFlyModeFlag = true;
|
||||
}
|
||||
|
||||
Global.ControlPicking = true;
|
||||
@@ -1390,6 +1474,7 @@ void editor_mode::enter()
|
||||
void editor_mode::exit()
|
||||
{
|
||||
EditorModeFlag = false;
|
||||
Global.editor_ortho = false; // the plan view must not follow us out of the editor
|
||||
Global.ControlPicking = m_statebackup.picking;
|
||||
FreeFlyModeFlag = m_statebackup.freefly;
|
||||
Global.pCamera = m_statebackup.camera;
|
||||
@@ -1470,7 +1555,12 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
|
||||
|
||||
if (!Global.ctrlState && !Global.shiftState)
|
||||
{
|
||||
Application.pop_mode();
|
||||
// when started straight in the editor there's no mode to fall back to, and popping the last
|
||||
// one would leave the rest of the frame reading an empty stack
|
||||
if (Global.editor_startup)
|
||||
Application.queue_quit(true);
|
||||
else
|
||||
Application.pop_mode();
|
||||
}
|
||||
else if (Global.ctrlState && Global.shiftState)
|
||||
{
|
||||
@@ -1553,6 +1643,14 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
|
||||
return;
|
||||
}
|
||||
|
||||
// while the plan tool is up the left button belongs to it: it lays and moves the points of the
|
||||
// track layout, so it must not also pick or place scenery nodes
|
||||
if (Global.editor_ortho && Button == GLFW_MOUSE_BUTTON_LEFT)
|
||||
{
|
||||
m_input.mouse.button(Button, Action);
|
||||
return;
|
||||
}
|
||||
|
||||
// in chunk-edit mode the left button adds a neighbouring chunk (Shift = delete the clicked one)
|
||||
if (m_chunk_edit && Button == GLFW_MOUSE_BUTTON_LEFT)
|
||||
{
|
||||
|
||||
@@ -60,10 +60,7 @@ class editor_mode : public application_mode
|
||||
void on_key(int Key, int Scancode, int Action, int Mods) override;
|
||||
void on_cursor_pos(double Horizontal, double Vertical) override;
|
||||
void on_mouse_button(int Button, int Action, int Mods) override;
|
||||
void on_scroll(double const Xoffset, double const Yoffset) override
|
||||
{
|
||||
;
|
||||
}
|
||||
void on_scroll(double const Xoffset, double const Yoffset) override;
|
||||
void on_window_resize(int w, int h) override
|
||||
{
|
||||
;
|
||||
@@ -129,6 +126,10 @@ class editor_mode : public application_mode
|
||||
editormode_input m_input;
|
||||
static TCamera Camera;
|
||||
|
||||
// camera orientation from before the plan view took over, to be handed back when it is left
|
||||
bool m_orthoactive{false};
|
||||
glm::vec3 m_orthoangle{0.0f};
|
||||
|
||||
// focus (smooth camera fly-to) state
|
||||
static bool m_focus_active;
|
||||
glm::dvec3 m_focus_start_pos{0.0,0.0,0.0};
|
||||
|
||||
@@ -12,6 +12,7 @@ module;
|
||||
#include <sstream>
|
||||
#include <type_traits>
|
||||
#include <string>
|
||||
#include "imgui/imgui.h"
|
||||
#include "utilities/Globals_macros.h"
|
||||
#include "utilities/utilities_macros.h"
|
||||
|
||||
@@ -40,6 +41,7 @@ editor_ui::editor_ui()
|
||||
add_external_panel(&m_nodebankpanel);
|
||||
add_external_panel(&m_functionspanel);
|
||||
add_external_panel(&m_brushobjects);
|
||||
add_external_panel(&m_planpanel);
|
||||
}
|
||||
|
||||
// updates state of UI elements
|
||||
@@ -64,6 +66,17 @@ void editor_ui::update()
|
||||
m_brushobjects.update(*ptr);
|
||||
}
|
||||
|
||||
void editor_ui::render_menu_contents()
|
||||
{
|
||||
ui_layer::render_menu_contents();
|
||||
|
||||
if (ImGui::BeginMenu("Track layout"))
|
||||
{
|
||||
ImGui::MenuItem(m_planpanel.name().c_str(), nullptr, &m_planpanel.is_open);
|
||||
ImGui::EndMenu();
|
||||
}
|
||||
}
|
||||
|
||||
void editor_ui::toggleBrushSettings(bool isVisible)
|
||||
{
|
||||
if (m_brushobjects.is_open != isVisible)
|
||||
|
||||
@@ -17,6 +17,9 @@ export module eu07.simcore:editoruilayer;
|
||||
import :fwd;
|
||||
import eu07.application.uilayer;
|
||||
import :editoruipanels;
|
||||
// the plan tool is a module of its own: it stands on the editor's own libraries, not on the
|
||||
// simulation core, and the panel is held here by value
|
||||
export import eu07.application.planpanel;
|
||||
|
||||
export {
|
||||
/*
|
||||
@@ -55,12 +58,16 @@ class editor_ui : public ui_layer
|
||||
float getSpacing();
|
||||
void toggleBrushSettings(bool isVisible);
|
||||
|
||||
protected:
|
||||
void render_menu_contents() override;
|
||||
|
||||
private:
|
||||
// members
|
||||
itemproperties_panel m_itempropertiespanel{"Node Properties", true};
|
||||
functions_panel m_functionspanel{"Functions", true};
|
||||
nodebank_panel m_nodebankpanel{"Node Bank", true};
|
||||
brush_object_list m_brushobjects{"Brush properties", false};
|
||||
plan_panel m_planpanel{"Plan", false};
|
||||
scene::basic_node *m_node{nullptr}; // currently bound scene node, if any
|
||||
};
|
||||
|
||||
|
||||
3358
application/planpanel.cpp
Normal file
3358
application/planpanel.cpp
Normal file
File diff suppressed because it is too large
Load Diff
220
application/planpanel.cppm
Normal file
220
application/planpanel.cppm
Normal file
@@ -0,0 +1,220 @@
|
||||
/*
|
||||
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/.
|
||||
*/
|
||||
module;
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include "imgui/imgui.h"
|
||||
#include "maj0sted/editor/tile_cache.hpp"
|
||||
#include "maj0sted/editor/layout.hpp"
|
||||
#include "maj0sted/editor/model.hpp"
|
||||
#include "maj0sted/editor/render.hpp"
|
||||
#include "maj0sted/editor/solution.hpp"
|
||||
#include "maj0sted/io/scn_export.hpp"
|
||||
|
||||
export module eu07.application.planpanel;
|
||||
import eu07.application.uilayer;
|
||||
import eu07.editor.orthophoto;
|
||||
|
||||
export {
|
||||
|
||||
// track layout tool. the drawing happens in the viewport, which the tool puts into a top-down
|
||||
// orthographic plan view; the panel holds only the controls. none of the geometry is computed here:
|
||||
// maj0sted solves the whole document and the panel paints what comes back.
|
||||
//
|
||||
// a track is a chain of elements, and every one of them says outright what it is: prosta of so many
|
||||
// metres, luk of radius R turning so far, krzywa przejsciowa easing to R over so many metres. there
|
||||
// is no second language of "run on until you reach that line" - laying by clicking along the ground
|
||||
// works out the corner at the click and writes the numbers down.
|
||||
//
|
||||
// what one track keeps against another is said as a relation, not redrawn by hand: an element may be
|
||||
// held parallel to another track's element at a given miedzytorze, and the solver moves the whole
|
||||
// track until it really is.
|
||||
class plan_panel : public ui_panel
|
||||
{
|
||||
|
||||
public:
|
||||
plan_panel(std::string const &Name, bool const Isopen);
|
||||
|
||||
void update() override;
|
||||
void render_contents() override;
|
||||
|
||||
private:
|
||||
using Document = maj0sted::editor::Document;
|
||||
using Element = maj0sted::editor::Element;
|
||||
using ElementId = maj0sted::editor::ElementId;
|
||||
using Solution = maj0sted::editor::Solution;
|
||||
using Track = maj0sted::editor::Track;
|
||||
using TrackId = maj0sted::editor::TrackId;
|
||||
using TurnoutId = maj0sted::editor::TurnoutId;
|
||||
|
||||
// panels
|
||||
void render_toolbar();
|
||||
void render_elements();
|
||||
void render_turnouts();
|
||||
// joining two tracks into one: the radius a join may bend at, and the button that starts
|
||||
// pointing at the ends to join. everything else about it happens on the map
|
||||
void render_join();
|
||||
void render_turnout_type(maj0sted::editor::TurnoutType &Type_);
|
||||
// the catalogue drawing of the chosen type, in its own window: the diverging path piece by
|
||||
// piece, both tracks' rails, and the points a drawing is dimensioned from
|
||||
void render_template_window();
|
||||
void render_diagnostics();
|
||||
void render_storage();
|
||||
void export_scn(bool Run_);
|
||||
void run_scn();
|
||||
void seed_catalogue();
|
||||
void render_newmap_dialog();
|
||||
void render_location_dialog();
|
||||
|
||||
// scene
|
||||
void handle_scene();
|
||||
void draw_on_scene();
|
||||
void go_to_plan();
|
||||
|
||||
// document
|
||||
void solve();
|
||||
void start_map(bool const Georeferenced, double const Originx, double const Originy);
|
||||
Track *current_track();
|
||||
Track const *current_track() const;
|
||||
void new_track();
|
||||
void delete_current_track();
|
||||
void clear_selection();
|
||||
// carries the track on to (X,Y). the track ends where it was last clicked, so that end is the
|
||||
// corner: it gets rounded with an arc of m_corner_radius, the straight behind it gives up the
|
||||
// tangent, and the new straight runs out to (X,Y) exactly. all of it lands in the document as
|
||||
// ordinary elements with ordinary lengths
|
||||
void append_point_to(double const X, double const Y);
|
||||
// takes back one click: the trailing straight, its corner arc, and the tangent the arc had
|
||||
// taken off the straight before it - which leaves the track ending on the click before
|
||||
void drop_last_vertex();
|
||||
// appends one element to the end of the current track, exactly as asked for
|
||||
void append_element(maj0sted::editor::Kind const Kind);
|
||||
void drop_last_element();
|
||||
void delete_selected_element();
|
||||
void place_turnout_at(TrackId const On, double const Wx, double const Wy);
|
||||
// re-lays the edited track along the lines its straights sat on before the change, so a new
|
||||
// radius (or a new transition length) is absorbed by the corner it belongs to and nothing
|
||||
// further down the track moves. false, with a reason, when it cannot be laid that way
|
||||
bool refit_current_track(std::string &Why);
|
||||
// swings the grabbed straight onto the line through its far end and (X,Y), then re-fits the
|
||||
// arcs on either side of it. leaves the track untouched when that cannot be laid
|
||||
void drag_straight_to(double const X, double const Y);
|
||||
|
||||
// hit tests, in screen pixels
|
||||
// the end of a straight of the edited track: OutIndex is its element index, OutEnd 0 for the
|
||||
// end it starts on and 1 for the end it runs out to
|
||||
bool hit_straight_end(ImVec2 const &Mouse, std::size_t &OutIndex, int &OutEnd) const;
|
||||
bool hit_element(ImVec2 const &Mouse, ElementId &OutElement, TrackId &OutTrack) const;
|
||||
bool hit_turnout(ImVec2 const &Mouse, TurnoutId &OutTurnout) const;
|
||||
// the loose end of any track: OutEnd 0 for the one it starts on, 1 for the one it runs out to.
|
||||
// a track hanging on a turnout's port has no free start, so that one is not offered
|
||||
bool hit_track_end(ImVec2 const &Mouse, float const Tolerance, TrackId &OutTrack, int &OutEnd) const;
|
||||
// whether that end is loose at all: one that already stands on another track's end is joined
|
||||
// to it, and there is nothing to join twice
|
||||
bool loose_end(maj0sted::editor::SolvedTrack const &Track_, int const End_) const;
|
||||
// how an end reads for a join laid out of it or into it: one facing the wrong way for that
|
||||
// is taken backwards, which is what turning the track round would do to it anyway
|
||||
static maj0sted::domain::geometry::Pose join_pose(maj0sted::editor::SolvedTrack const &Track_, int const End_, bool const Leaving_);
|
||||
// lays the join and leaves one track, saying what it cost
|
||||
void join_ends(TrackId const A, int const Aend, TrackId const B, int const Bend);
|
||||
// nearest station along a solved track's axis, for placing and sliding turnouts
|
||||
bool station_on(TrackId const Track, double const Wx, double const Wy, double &OutStation) const;
|
||||
TrackId nearest_track_axis(ImVec2 const &Mouse, float const Tolerance) const;
|
||||
|
||||
// members
|
||||
Document m_document;
|
||||
Solution m_solution;
|
||||
std::vector<maj0sted::editor::PlanPolyline> m_rails;
|
||||
|
||||
TrackId m_track{};
|
||||
ElementId m_sel_element{};
|
||||
TurnoutId m_sel_turnout{};
|
||||
|
||||
// a press on the ground does whatever is under it: picks up what is drawn there, or carries the
|
||||
// current track on to there. every transient errand (placing a turnout, picking the element to
|
||||
// run parallel to) is started from its own button, and Esc drops back out of it
|
||||
bool m_dragging_turnout{false};
|
||||
|
||||
// grabbing a straight by one of its ends: which one, and the far end it swings about. the far
|
||||
// end is captured once, at the grab, because re-fitting the arc behind it moves it
|
||||
bool m_dragging_straight{false};
|
||||
std::size_t m_drag_index{0};
|
||||
int m_drag_end{0};
|
||||
double m_pivot_x{0.0};
|
||||
double m_pivot_y{0.0};
|
||||
|
||||
// an empty track needs two clicks before there is anything to carry on: the first only marks
|
||||
// where it starts. after that the track always ends on the last click, which is the corner the
|
||||
// next one is rounded at
|
||||
bool m_pending{false};
|
||||
double m_startx{0.0};
|
||||
double m_starty{0.0};
|
||||
|
||||
// 0 idle, 1 waiting for a click on the through track
|
||||
int m_pick_turnout{0};
|
||||
// 0 idle, 1 waiting for the element the selected one is to be held parallel to
|
||||
int m_pick_parallel{0};
|
||||
// scalanie by pointing at it: 0 idle, 1 waiting for the first end, 2 for the second. a track
|
||||
// joins onto another end to start, so which of the two ends is clicked says which way round
|
||||
int m_pick_join{0};
|
||||
TrackId m_join_first{};
|
||||
int m_join_first_end{0};
|
||||
|
||||
// what the next appended element is made of
|
||||
double m_new_radius{300.0};
|
||||
double m_new_length{100.0};
|
||||
int m_new_hand{1};
|
||||
// the radius a corner is rounded with when laying by clicking
|
||||
double m_corner_radius{300.0};
|
||||
// the radius a join may bend at
|
||||
double m_join_radius{300.0};
|
||||
// miedzytorze a new hold is given, metres
|
||||
double m_new_offset{4.75};
|
||||
|
||||
int m_turnout_type{0};
|
||||
// which piece of the chosen type's geometry list is open for editing; -1 = none
|
||||
int m_sel_piece{-1};
|
||||
|
||||
// the template drawing. a switch is thirty metres long and a metre and a half wide, so like
|
||||
// every catalogue drawing this one is stretched across the track - the ratio is said on screen
|
||||
bool m_show_template{false};
|
||||
float m_tpl_zoom{1.0f};
|
||||
float m_tpl_pan{0.0f}; // metres off centre, along the through track
|
||||
float m_tpl_exagg{6.0f}; // przewyzszenie poprzeczne
|
||||
bool m_tpl_rails{true};
|
||||
bool m_tpl_marks{true};
|
||||
// lukowanie in the drawing: the radius the tor zasadniczy is bent to, and which way
|
||||
double m_tpl_bend_radius{0.0};
|
||||
int m_tpl_bend_hand{0};
|
||||
|
||||
char m_namebuf[128]{};
|
||||
std::string m_status;
|
||||
char m_path[256]{"editor/plan.m0s"};
|
||||
char m_scn_path[256]{"scenery/plan_export.scn"};
|
||||
std::vector<std::string> m_scn_warnings;
|
||||
// the exported scenery is opened in the editor by default: it stands up without a locomotive,
|
||||
// and looking at what was drawn is what the export is usually for
|
||||
bool m_scn_run_editor{true};
|
||||
bool m_scn_trainset{true};
|
||||
|
||||
bool m_pickingplace{false};
|
||||
double m_pickx{0.0};
|
||||
double m_picky{0.0};
|
||||
double m_mapviewx{0.0};
|
||||
double m_mapviewy{0.0};
|
||||
double m_mapscale{0.0};
|
||||
|
||||
editor::wms_image m_topomap{maj0sted::editor::WmsConfig::geoportal_topo()};
|
||||
editor::orthophoto_source m_ortho;
|
||||
// 1 km topo cells for views wider than a kilometre (orto's 100 m grid is too dense there)
|
||||
editor::orthophoto_source m_topo{maj0sted::editor::WmsConfig::geoportal_topo(), 1000, "topo"};
|
||||
bool m_showortho{true};
|
||||
};
|
||||
|
||||
} // export
|
||||
@@ -73,14 +73,24 @@ bool scenarioloader_mode::update() {
|
||||
}
|
||||
catch (invalid_scenery_exception &e) {
|
||||
ErrorLog( "Bad init: scenario loading failed" );
|
||||
state.reset();
|
||||
Application.pop_mode();
|
||||
}
|
||||
|
||||
WriteLog( "Scenario loading time: " + std::to_string( std::chrono::duration_cast<std::chrono::seconds>( std::chrono::system_clock::now() - timestart ).count() ) + " seconds" );
|
||||
// TODO: implement and use next mode cue
|
||||
|
||||
// the mode object is kept for the next time a scenery is loaded; without dropping the context
|
||||
// here, a later enter() would skip deserialize_begin and finish an already-spent parser again
|
||||
state.reset();
|
||||
|
||||
Application.pop_mode();
|
||||
Application.push_mode( eu07_application::mode::driver );
|
||||
// starting into the editor the simulation never runs at all: there's no driver mode underneath, so
|
||||
// no physics, no AI and no timetables - just the loaded world to work on
|
||||
Application.push_mode(
|
||||
Global.editor_startup ?
|
||||
eu07_application::mode::editor :
|
||||
eu07_application::mode::driver );
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -94,6 +104,11 @@ void scenarioloader_mode::enter() {
|
||||
// TBD: hide cursor in fullscreen mode?
|
||||
Application.set_cursor( GLFW_CURSOR_NORMAL );
|
||||
|
||||
// a new map from the editor re-enters this mode while the previous load's context is still
|
||||
// sitting in `state`; clear it so update() opens the file again instead of re-finishing an
|
||||
// exhausted parser (Groups.close / create_map_geometry on an already-built region)
|
||||
state.reset();
|
||||
|
||||
simulation::is_ready = false;
|
||||
|
||||
Application.set_title( Global.AppName + " (" + Global.SceneryFile + ")" );
|
||||
|
||||
Reference in New Issue
Block a user