17
0
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:
maj00r
2026-09-13 12:36:21 +02:00
74 changed files with 13615 additions and 81 deletions

View File

@@ -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;
}

View File

@@ -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)
{

View File

@@ -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};

View File

@@ -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)

View File

@@ -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

File diff suppressed because it is too large Load Diff

220
application/planpanel.cppm Normal file
View 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

View File

@@ -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 + ")" );