17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-28 18:07:29 +01:00

feat(editor): edit session (-edit) and scenery layers

This commit is contained in:
2026-10-02 21:29:18 +02:00
parent 69f5f551a1
commit 73f2f68aa0
19 changed files with 560 additions and 3 deletions

View File

@@ -1145,6 +1145,16 @@ int eu07_application::init_settings(int Argc, char *Argv[])
Global.local_start_vehicle = ToLower(Argv[++i]);
}
}
else if (token == "-edit")
{
// edit session: the scenery is loaded without player vehicle, straight into the editor
if (i + 1 < Argc)
{
Global.SceneryFile = ToLower(Argv[++i]);
Global.local_start_vehicle = "ghostview";
Global.editor_session = true;
}
}
else if (token == "-seed")
{
// a number is the seed; anything else is a word that stands for one. said in the
@@ -1159,6 +1169,7 @@ int eu07_application::init_settings(int Argc, char *Argv[])
{
std::cout << "usage: " << std::string(Argv[0]) << " [-s sceneryfilepath]"
<< " [-v vehiclename]"
<< " [-edit sceneryfilepath]"
<< " [-seed number|word]" << std::endl;
return -1;
}

View File

@@ -412,6 +412,14 @@ bool driver_mode::update()
simulation::is_ready = simulation::is_ready || (simulation::Train != nullptr && simulation::Train->is_cab_initialized) || Global.local_start_vehicle == "ghostview";
if (m_editorpending)
{
// edit session starts in the editor. the switch is made after the initial update on purpose, so the
// simulation state is set up the same way as when the editor is entered by hand right after the load
m_editorpending = false;
Application.push_mode(eu07_application::mode::editor);
}
return true;
}
@@ -458,6 +466,8 @@ void driver_mode::enter()
Timer::ResetTimers();
set_picking(!Global.captureonstart);
m_editorpending = Global.editor_session;
}
// maintenance method, called when the mode is deactivated

View File

@@ -116,4 +116,5 @@ private:
int iPause { 0 }; // wykrywanie zmian w zapauzowaniu
command_relay m_relay;
std::string change_train; // train name awaiting entering
bool m_editorpending { false }; // scenery was opened for editing, switch to the editor after the initial update
};

View File

@@ -25,6 +25,7 @@ http://mozilla.org/MPL/2.0/.
#include "model/Model3d.h"
#include "utilities/Float3d.h"
#include "scene/scene.h"
#include "scene/scenelayers.h"
#include "utilities/parser.h"
#include "utilities/utilities.h"
@@ -891,6 +892,14 @@ bool editor_mode::update()
// variable step routines
update_camera(deltarealtime);
// drop the selection if its layer was hidden or locked in the meantime
if (m_node != nullptr && false == scene::Layers.editable(m_node->layer()))
{
m_node = nullptr;
m_dragging = false;
ui()->set_node(nullptr);
}
simulation::Region->update_sounds();
audio::renderer.update(Global.iPause ? 0.0 : deltarealtime);
@@ -1522,10 +1531,24 @@ void editor_mode::save_scene_with_terrain()
m_streamer.flush(); // save resident edited chunks to disk
// export scenery; the exported .scm now carries an `editorterrain` directive (streamer is active)
simulation::State.export_as_text(Global.SceneryFile);
export_scenery();
WriteLog("Editor: saved scene + terrain", logtype::generic);
}
void editor_mode::export_scenery()
{
// hiding a layer clears the visibility flag of its nodes, and the flag is a part of exported node data.
// show the hidden layers for the duration of the export, so the files get the visibility defined in the scenery
auto const hiddenlayers = scene::Layers.hidden();
for (auto const layer : hiddenlayers)
scene::Layers.visible(layer, true);
simulation::State.export_as_text(Global.SceneryFile);
for (auto const layer : hiddenlayers)
scene::Layers.visible(layer, false);
}
void editor_mode::handle_terrain_sculpt(double Deltatime)
{
// world point under the cursor (Mouse_Position is camera-relative, like the brush placement uses)
@@ -2126,6 +2149,7 @@ void editor_mode::exit()
m_fill_last.clear();
m_orthophoto.cancel_pending();
m_orthophoto.detach_scene(); // the layer is an editor aid, keep it out of the other modes
scene::Layers.show_all(); // likewise for hidden scenery layers, the other modes get the scenery as it was defined
// drop selection so a stale/dangling node pointer isn't used on the next editor session
m_node = nullptr;
@@ -2204,7 +2228,7 @@ void editor_mode::on_key(int const Key, int const Scancode, int const Action, in
}
else if (Global.ctrlState && Global.shiftState)
{
simulation::State.export_as_text(Global.SceneryFile);
export_scenery();
}
break;
@@ -2340,6 +2364,10 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
m_node = nullptr;
// nodes of locked layers can't be selected
if (node && false == scene::Layers.editable(node->layer()))
node = nullptr;
// ignore picks that are beyond allowed placement distance
if (node) {
double const dist = glm::distance(node->location(), glm::dvec3{Global.pCamera.Pos});

View File

@@ -162,6 +162,8 @@ class editor_mode : public application_mode
void handle_chunk_edit_click(bool DeleteMode);
// commits authored terrain to disk, enables streaming, and exports the scenery (Ctrl+S)
void save_scene_with_terrain();
// exports the scenery in legacy (text) format, with layers hidden in the editor left out of the picture
void export_scenery();
// raises/lowers terrain under the cursor while the left mouse button is held in sculpt mode
void handle_terrain_sculpt(double Deltatime);
// returns the terrain patch (if any) whose footprint covers the given world point

View File

@@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Globals.h"
#include "utilities/utilities.h"
#include "scene/scenenode.h"
#include "scene/scenelayers.h"
#include "rendering/renderer.h"
editor_ui::editor_ui()
@@ -99,6 +100,98 @@ void editor_ui::render_header_sections()
m_itempropertiespanel.render_body();
ImGui::Unindent();
}
if (ImGui::CollapsingHeader("Layers"))
{
ImGui::Indent();
render_layers();
ImGui::Unindent();
}
}
void editor_ui::render_layers()
{
if (scene::Layers.empty())
{
// layers are established only when the scenery is loaded for editing, regular load skips the bookkeeping
ImGui::TextDisabled("Scenery layers are available in an edit session.\nStart the simulator with: -edit <scenery file>");
return;
}
ImGui::TextDisabled("visible, locked, layer file (click: make active)");
std::pair<char const *, scene::layer_item> const itemlabels[] = {{"models", scene::layer_item::model},
{"tracks", scene::layer_item::track},
{"traction", scene::layer_item::traction},
{"power sources", scene::layer_item::powersource},
{"memory cells", scene::layer_item::memcell},
{"event launchers", scene::layer_item::launcher},
{"events", scene::layer_item::event},
{"vehicles", scene::layer_item::vehicle},
{"sounds", scene::layer_item::sound},
{"terrain shapes", scene::layer_item::shape},
{"lines", scene::layer_item::lines}};
for (std::size_t idx = 1; idx <= scene::Layers.size(); ++idx)
{
auto const handle{static_cast<scene::layer_handle>(idx)};
auto const &layer{scene::Layers.layer(handle)};
auto const isactive{handle == scene::Layers.active()};
ImGui::PushID(static_cast<int>(idx));
// active layer receives new nodes, so it can't be hidden nor locked
if (isactive)
{
ImGui::PushStyleVar(ImGuiStyleVar_Alpha, ImGui::GetStyle().Alpha * 0.5f);
}
auto visible{layer.visible};
if (ImGui::Checkbox("##visible", &visible) && false == isactive)
{
scene::Layers.visible(handle, visible);
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", isactive ? "Active layer is always visible" : "Show or hide models, tracks and traction of the layer");
}
ImGui::SameLine();
auto locked{layer.locked};
if (ImGui::Checkbox("##locked", &locked) && false == isactive)
{
scene::Layers.locked(handle, locked);
}
if (ImGui::IsItemHovered())
{
ImGui::SetTooltip("%s", isactive ? "Active layer can't be locked" : "Nodes of locked layer can't be selected in the viewport");
}
if (isactive)
{
ImGui::PopStyleVar();
}
ImGui::SameLine();
// layers are listed in the order the files were first opened, so indentation alone shows the include tree
auto const label{std::string(2 * scene::Layers.depth(handle), ' ') + Bezogonkow(layer.name) + " (" + (layer.binary ? "binary terrain" : std::to_string(layer.item_count())) + ")"};
if (ImGui::Selectable(label.c_str(), isactive))
{
scene::Layers.active(handle);
}
if (ImGui::IsItemHovered())
{
std::string content{isactive ? "Active layer, nodes created in the editor are placed here" : "Click to make this the active layer"};
if (layer.binary)
{
content += "\nThe file wasn't parsed, its content was loaded from binary terrain file";
}
for (auto const &itemlabel : itemlabels)
{
auto const count{layer.items[static_cast<std::size_t>(itemlabel.second)]};
if (count > 0)
{
content += "\n" + std::string{itemlabel.first} + ": " + std::to_string(count);
}
}
ImGui::SetTooltip("%s", content.c_str());
}
ImGui::PopID();
}
}
void editor_ui::render_rotation_controls()

View File

@@ -58,6 +58,8 @@ class editor_ui : public ui_layer
// methods
void render_mode_options(nodebank_panel::edit_mode const Mode);
void render_header_sections();
// list of scenery layers: visibility, selection lock and choice of the layer receiving new nodes
void render_layers();
// members
itemproperties_panel m_itempropertiespanel{"Node Properties", true}; // not a window of its own, drawn in the toolset window
functions_panel m_functionspanel{"Functions", true}; // not a window of its own, its settings are drawn in the toolset tabs

View File

@@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/.
#include "stdafx.h"
#include "application/editoruipanels.h"
#include "scene/scenenodegroups.h"
#include "scene/scenelayers.h"
#include "utilities/Globals.h"
#include "vehicle/Camera.h"
@@ -78,6 +79,10 @@ void itemproperties_panel::update(scene::basic_node *Node)
textline = "name: " + (node->name().empty() ? "(none)" : Bezogonkow(node->name())) + "\ntype: " + node->node_type + "\nlocation: [" + to_string(node->location().x, 2) + ", " + to_string(node->location().y, 2) + ", " +
to_string(node->location().z, 2) + "]" +
" (distance: " + to_string(glm::length(glm::dvec3{node->location().x, 0.0, node->location().z} - glm::dvec3{camera.Pos.x, 0.0, camera.Pos.z}), 1) + " m)" + "\nUUID: " + node->uuid.to_string();
if (scene::Layers.valid(node->layer()))
{
textline += "\nlayer: " + Bezogonkow(scene::Layers.layer(node->layer()).name);
}
text_lines.emplace_back(textline, Global.UITextColor);
// subclass-specific data