17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-04 03:02:05 +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

@@ -178,6 +178,7 @@ set(SOURCES
"application/scenarioloadermode.cpp" "application/scenarioloadermode.cpp"
"application/scenarioloaderuilayer.cpp" "application/scenarioloaderuilayer.cpp"
"scene/scenenodegroups.cpp" "scene/scenenodegroups.cpp"
"scene/scenelayers.cpp"
"simulation/simulationenvironment.cpp" "simulation/simulationenvironment.cpp"
"simulation/simulationstateserializer.cpp" "simulation/simulationstateserializer.cpp"
"rendering/precipitation.cpp" "rendering/precipitation.cpp"

View File

@@ -1145,6 +1145,16 @@ int eu07_application::init_settings(int Argc, char *Argv[])
Global.local_start_vehicle = ToLower(Argv[++i]); 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") else if (token == "-seed")
{ {
// a number is the seed; anything else is a word that stands for one. said in the // 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]" std::cout << "usage: " << std::string(Argv[0]) << " [-s sceneryfilepath]"
<< " [-v vehiclename]" << " [-v vehiclename]"
<< " [-edit sceneryfilepath]"
<< " [-seed number|word]" << std::endl; << " [-seed number|word]" << std::endl;
return -1; 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"; 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; return true;
} }
@@ -458,6 +466,8 @@ void driver_mode::enter()
Timer::ResetTimers(); Timer::ResetTimers();
set_picking(!Global.captureonstart); set_picking(!Global.captureonstart);
m_editorpending = Global.editor_session;
} }
// maintenance method, called when the mode is deactivated // maintenance method, called when the mode is deactivated

View File

@@ -116,4 +116,5 @@ private:
int iPause { 0 }; // wykrywanie zmian w zapauzowaniu int iPause { 0 }; // wykrywanie zmian w zapauzowaniu
command_relay m_relay; command_relay m_relay;
std::string change_train; // train name awaiting entering 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 "model/Model3d.h"
#include "utilities/Float3d.h" #include "utilities/Float3d.h"
#include "scene/scene.h" #include "scene/scene.h"
#include "scene/scenelayers.h"
#include "utilities/parser.h" #include "utilities/parser.h"
#include "utilities/utilities.h" #include "utilities/utilities.h"
@@ -891,6 +892,14 @@ bool editor_mode::update()
// variable step routines // variable step routines
update_camera(deltarealtime); 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(); simulation::Region->update_sounds();
audio::renderer.update(Global.iPause ? 0.0 : deltarealtime); 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 m_streamer.flush(); // save resident edited chunks to disk
// export scenery; the exported .scm now carries an `editorterrain` directive (streamer is active) // 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); 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) void editor_mode::handle_terrain_sculpt(double Deltatime)
{ {
// world point under the cursor (Mouse_Position is camera-relative, like the brush placement uses) // 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_fill_last.clear();
m_orthophoto.cancel_pending(); m_orthophoto.cancel_pending();
m_orthophoto.detach_scene(); // the layer is an editor aid, keep it out of the other modes 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 // drop selection so a stale/dangling node pointer isn't used on the next editor session
m_node = nullptr; 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) else if (Global.ctrlState && Global.shiftState)
{ {
simulation::State.export_as_text(Global.SceneryFile); export_scenery();
} }
break; break;
@@ -2340,6 +2364,10 @@ void editor_mode::on_mouse_button(int const Button, int const Action, int const
m_node = nullptr; 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 // ignore picks that are beyond allowed placement distance
if (node) { if (node) {
double const dist = glm::distance(node->location(), glm::dvec3{Global.pCamera.Pos}); 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); void handle_chunk_edit_click(bool DeleteMode);
// commits authored terrain to disk, enables streaming, and exports the scenery (Ctrl+S) // commits authored terrain to disk, enables streaming, and exports the scenery (Ctrl+S)
void save_scene_with_terrain(); 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 // raises/lowers terrain under the cursor while the left mouse button is held in sculpt mode
void handle_terrain_sculpt(double Deltatime); void handle_terrain_sculpt(double Deltatime);
// returns the terrain patch (if any) whose footprint covers the given world point // 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/Globals.h"
#include "utilities/utilities.h" #include "utilities/utilities.h"
#include "scene/scenenode.h" #include "scene/scenenode.h"
#include "scene/scenelayers.h"
#include "rendering/renderer.h" #include "rendering/renderer.h"
editor_ui::editor_ui() editor_ui::editor_ui()
@@ -99,6 +100,98 @@ void editor_ui::render_header_sections()
m_itempropertiespanel.render_body(); m_itempropertiespanel.render_body();
ImGui::Unindent(); 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() void editor_ui::render_rotation_controls()

View File

@@ -58,6 +58,8 @@ class editor_ui : public ui_layer
// methods // methods
void render_mode_options(nodebank_panel::edit_mode const Mode); void render_mode_options(nodebank_panel::edit_mode const Mode);
void render_header_sections(); void render_header_sections();
// list of scenery layers: visibility, selection lock and choice of the layer receiving new nodes
void render_layers();
// members // members
itemproperties_panel m_itempropertiespanel{"Node Properties", true}; // not a window of its own, drawn in the toolset window 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 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 "stdafx.h"
#include "application/editoruipanels.h" #include "application/editoruipanels.h"
#include "scene/scenenodegroups.h" #include "scene/scenenodegroups.h"
#include "scene/scenelayers.h"
#include "utilities/Globals.h" #include "utilities/Globals.h"
#include "vehicle/Camera.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) + ", " + 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) + "]" + 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(); " (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); text_lines.emplace_back(textline, Global.UITextColor);
// subclass-specific data // subclass-specific data

194
scene/scenelayers.cpp Normal file
View File

@@ -0,0 +1,194 @@
/*
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 "scene/scenelayers.h"
#include "simulation/simulation.h"
#include "model/AnimModel.h"
#include "world/Track.h"
#include "world/Traction.h"
namespace scene
{
node_layers Layers;
std::size_t basic_layer::item_count() const
{
return std::accumulate(std::begin(items), std::end(items), std::size_t{0});
}
// indicates start of content of specified scenery file. returns: handle to the layer of the file
layer_handle node_layers::open(std::string const &Name)
{
// the same file can be included more than once, all its content goes to a single layer then
auto const lookup{std::find_if(std::begin(m_layers), std::end(m_layers), [&](basic_layer const &Layer) { return Layer.name == Name; })};
auto layerhandle{static_cast<layer_handle>(std::distance(std::begin(m_layers), lookup) + 1)};
if (lookup == std::end(m_layers))
{
if (m_layers.size() < static_cast<std::size_t>(std::numeric_limits<layer_handle>::max()))
{
m_layers.emplace_back();
m_layers.back().name = Name;
m_layers.back().parent = handle();
}
else
{
// out of handles, content of the file is attributed to the parent layer
layerhandle = handle();
}
}
// NOTE: the stack receives an entry even if the layer wasn't created, to keep it in sync with close() calls
m_stack.emplace_back(layerhandle);
return layerhandle;
}
// indicates end of content of the current scenery file. returns: handle to the parent layer, or null_handle if the stack is empty
layer_handle node_layers::close()
{
if (false == m_stack.empty())
{
m_stack.pop_back();
}
return handle();
}
void node_layers::clear()
{
m_layers.clear();
m_stack.clear();
m_active = null_handle;
}
int node_layers::depth(layer_handle Layer) const
{
auto depth{0};
// NOTE: parent is always created before its children, which rules out cycles
while (valid(Layer) && layer(Layer).parent != null_handle && layer(Layer).parent < Layer)
{
Layer = layer(Layer).parent;
++depth;
}
return depth;
}
void node_layers::count(layer_handle const Layer, layer_item const Item, int const Change)
{
if (false == valid(Layer))
{
return;
}
auto &itemcount{layer(Layer).items[static_cast<std::size_t>(Item)]};
if (Change >= 0)
{
itemcount += static_cast<std::size_t>(Change);
}
else
{
itemcount -= std::min(itemcount, static_cast<std::size_t>(-Change));
}
}
void node_layers::active(layer_handle const Layer)
{
if (false == valid(Layer))
{
return;
}
// new items land in the active layer, they'd be out of reach right away if it was hidden or locked
visible(Layer, true);
locked(Layer, false);
m_active = Layer;
}
// shows or hides nodes of specified layer. visibility defined in the scenery is preserved, hidden layer only overrides it
void node_layers::visible(layer_handle const Layer, bool const Visible)
{
if (false == valid(Layer) || layer(Layer).visible == Visible)
{
return;
}
layer(Layer).visible = Visible;
auto const update_node = [Layer, Visible](basic_node *Node) {
if (Node == nullptr || Node->layer() != Layer)
{
return;
}
if (Visible)
{
if (Node->m_layerhidden)
{
Node->visible(true);
Node->m_layerhidden = false;
}
}
else if (Node->visible())
{
// nodes which are invisible on their own aren't marked, so they stay that way when the layer is shown again
Node->visible(false);
Node->m_layerhidden = true;
}
};
// only these node types are drawn by the renderers basing on their visibility flag
for (auto *instance : simulation::Instances.sequence())
{
update_node(instance);
}
for (auto *path : simulation::Paths.sequence())
{
update_node(path);
}
for (auto *traction : simulation::Traction.sequence())
{
update_node(traction);
}
}
void node_layers::locked(layer_handle const Layer, bool const Locked)
{
if (valid(Layer))
{
layer(Layer).locked = Locked;
}
}
// true if nodes of specified layer can be selected and modified in the editor
bool node_layers::editable(layer_handle const Layer) const
{
// nodes without a layer (regular load, or created by the simulation) aren't restricted
return false == valid(Layer) || (layer(Layer).visible && false == layer(Layer).locked);
}
std::vector<layer_handle> node_layers::hidden() const
{
std::vector<layer_handle> layers;
for (std::size_t idx = 0; idx < m_layers.size(); ++idx)
{
if (false == m_layers[idx].visible)
{
layers.emplace_back(static_cast<layer_handle>(idx + 1));
}
}
return layers;
}
void node_layers::show_all()
{
for (auto const hiddenlayer : hidden())
{
visible(hiddenlayer, true);
}
}
} // namespace scene
//---------------------------------------------------------------------------

117
scene/scenelayers.h Normal file
View File

@@ -0,0 +1,117 @@
/*
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 "scene/scenenode.h"
namespace scene
{
// kinds of scenery items a layer keeps count of
enum class layer_item
{
model,
track,
traction,
powersource,
memcell,
launcher,
event,
vehicle,
sound,
shape,
lines,
count_
};
// scenery layer: content of a single scenery file (.scn, .scm, .ctr...) other than an .inc node template
struct basic_layer
{
std::string name; // file name, relative to the scenery directory
layer_handle parent{null_handle}; // layer of the file this one was included from
bool visible{true};
bool locked{false}; // items of locked layer can't be selected in the editor
bool binary{false}; // the file wasn't parsed, its content came from binary terrain file
std::array<std::size_t, static_cast<std::size_t>(layer_item::count_)> items{}; // amount of items of each kind
std::size_t item_count() const;
};
// holds scenery layers, established during load of scenery opened for editing (-edit)
// NOTE: regular load doesn't create any layers, all nodes are left with null_handle
class node_layers
{
public:
// methods
// indicates start of content of specified scenery file. returns: handle to the layer of the file
layer_handle open(std::string const &Name);
// indicates end of content of the current scenery file. returns: handle to the parent layer, or null_handle if the stack is empty
layer_handle close();
// returns layer of the scenery file being loaded, or null_handle if there's none
layer_handle handle() const
{
return m_stack.empty() ? null_handle : m_stack.back();
}
// removes all layers; layer handles stored in the nodes aren't touched
void clear();
bool empty() const
{
return m_layers.empty();
}
std::size_t size() const
{
return m_layers.size();
}
bool valid(layer_handle const Layer) const
{
return Layer != null_handle && Layer <= m_layers.size();
}
// grants direct access to specified layer. NOTE: handle has to be valid
basic_layer &layer(layer_handle const Layer)
{
return m_layers[Layer - 1];
}
basic_layer const &layer(layer_handle const Layer) const
{
return m_layers[Layer - 1];
}
// number of layers between specified layer and the scenario file
int depth(layer_handle Layer) const;
// adjusts item count of specified layer
void count(layer_handle const Layer, layer_item const Item, int const Change = 1);
// layer receiving the items created in the editor, or null_handle if there's none
layer_handle active() const
{
return m_active;
}
// NOTE: active layer is made visible and unlocked
void active(layer_handle const Layer);
// shows or hides nodes of specified layer. visibility defined in the scenery is preserved, hidden layer only overrides it
void visible(layer_handle const Layer, bool const Visible);
void locked(layer_handle const Layer, bool const Locked);
// true if nodes of specified layer can be selected and modified in the editor
bool editable(layer_handle const Layer) const;
// handles of layers which are currently hidden
std::vector<layer_handle> hidden() const;
// restores visibility of all layers
void show_all();
private:
// members
std::vector<basic_layer> m_layers; // layer handle is index in the vector + 1
std::vector<layer_handle> m_stack; // helper, layers of scenery files being loaded
layer_handle m_active{null_handle};
};
extern node_layers Layers;
} // namespace scene
//---------------------------------------------------------------------------

View File

@@ -717,7 +717,8 @@ memory_node::deserialize( cParser &Input, node_data const &Nodedata ) {
basic_node::basic_node( scene::node_data const &Nodedata ) : basic_node::basic_node( scene::node_data const &Nodedata ) :
m_name( Nodedata.name ) m_name( Nodedata.name ),
m_layer( Nodedata.layer )
{ {
uuid = UID::random(); uuid = UID::random();
node_type = Nodedata.type; node_type = Nodedata.type;

View File

@@ -70,6 +70,7 @@ struct node_data {
double range_max { std::numeric_limits<double>::max() }; double range_max { std::numeric_limits<double>::max() };
std::string name; std::string name;
std::string type; std::string type;
layer_handle layer { null_handle }; // scenery layer the node is defined in, if any
}; };
// holds unique piece of geometry, covered with single material // holds unique piece of geometry, covered with single material
@@ -359,6 +360,10 @@ public:
group( scene::group_handle Group ); group( scene::group_handle Group );
scene::group_handle scene::group_handle
group() const; group() const;
void
layer( scene::layer_handle Layer );
scene::layer_handle
layer() const;
void void
mark_dirty() { m_dirty = true; } mark_dirty() { m_dirty = true; }
bool bool
@@ -375,6 +380,8 @@ public:
bool m_visible { true }; // visibility flag bool m_visible { true }; // visibility flag
std::string m_name; std::string m_name;
bool m_dirty { false }; bool m_dirty { false };
bool m_layerhidden { false }; // the node was visible until its scenery layer was hidden
scene::layer_handle m_layer { null_handle }; // scenery layer this node belongs to, if any
UID uuid; UID uuid;
private: private:
@@ -443,6 +450,18 @@ basic_node::group() const {
return m_group; return m_group;
} }
inline
void
basic_node::layer( scene::layer_handle Layer ) {
m_layer = Layer;
}
inline
scene::layer_handle
basic_node::layer() const {
return m_layer;
}
} // scene } // scene
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------

View File

@@ -24,6 +24,7 @@ http://mozilla.org/MPL/2.0/.
#include "rendering/lightarray.h" #include "rendering/lightarray.h"
#include "rendering/particles.h" #include "rendering/particles.h"
#include "scene/scene.h" #include "scene/scene.h"
#include "scene/scenelayers.h"
#include "vehicle/Train.h" #include "vehicle/Train.h"
#include "application/application.h" #include "application/application.h"
#include "utilities/Logs.h" #include "utilities/Logs.h"
@@ -493,6 +494,7 @@ TEventLauncher * state_manager::create_eventlauncher(const std::string &src, con
} }
void state_manager::delete_model(TAnimModel *model) { void state_manager::delete_model(TAnimModel *model) {
scene::Layers.count(model->layer(), scene::layer_item::model, -1);
Region->erase(model); Region->erase(model);
Instances.purge(model); Instances.purge(model);
} }

View File

@@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/.
#include "simulation/simulationsounds.h" #include "simulation/simulationsounds.h"
#include "simulation/simulationenvironment.h" #include "simulation/simulationenvironment.h"
#include "scene/scenenodegroups.h" #include "scene/scenenodegroups.h"
#include "scene/scenelayers.h"
#include "rendering/particles.h" #include "rendering/particles.h"
#include "world/Event.h" #include "world/Event.h"
#include "world/MemCell.h" #include "world/MemCell.h"
@@ -73,6 +74,14 @@ state_serializer::deserialize_begin( std::string const &Scenariofile ) {
} }
scene::Groups.create(); scene::Groups.create();
scene::Layers.clear();
if( true == Global.editor_session ) {
// scenery opened for editing: keep track of which scenery file defines each node.
// the scenario file itself is the root layer, and the default target for nodes created in the editor
state->input.sceneryLayers = true;
scene::Layers.active( scene::Layers.open( Scenariofile ) );
}
if( false == state->input.ok() ) if( false == state->input.ok() )
throw invalid_scenery_exception(); throw invalid_scenery_exception();
@@ -155,6 +164,7 @@ state_serializer::deserialize_continue(std::shared_ptr<deserializer_state> state
} }
scene::Groups.close(); scene::Groups.close();
scene::Layers.close();
scene::Groups.update_map(); scene::Groups.update_map();
Region->create_map_geometry(); Region->create_map_geometry();
@@ -345,6 +355,7 @@ state_serializer::deserialize_event( cParser &Input, scene::scratch_data &Scratc
if( true == simulation::Events.insert( event ) ) { if( true == simulation::Events.insert( event ) ) {
scene::Groups.insert( scene::Groups.handle(), event ); scene::Groups.insert( scene::Groups.handle(), event );
scene::Layers.count( scene::Layers.handle(), scene::layer_item::event );
} }
else { else {
delete event; delete event;
@@ -436,6 +447,7 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
>> nodedata.name >> nodedata.name
>> nodedata.type; >> nodedata.type;
if( nodedata.name == "none" ) { nodedata.name.clear(); } if( nodedata.name == "none" ) { nodedata.name.clear(); }
nodedata.layer = scene::Layers.handle();
// type-based deserialization. not elegant but it'll do // type-based deserialization. not elegant but it'll do
if( nodedata.type == "dynamic" ) { if( nodedata.type == "dynamic" ) {
@@ -643,6 +655,34 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch
simulation::Region->insert( sound ); simulation::Region->insert( sound );
} }
if( nodedata.layer != null_handle ) {
// scenery opened for editing: keep count of what the scenery file contains
// NOTE: node types which failed to load bail out earlier and aren't counted
static std::unordered_map<std::string, scene::layer_item> const itemtypes {
{ "dynamic", scene::layer_item::vehicle },
{ "track", scene::layer_item::track },
{ "traction", scene::layer_item::traction },
{ "tractionpowersource", scene::layer_item::powersource },
{ "model", scene::layer_item::model },
{ "triangles", scene::layer_item::shape },
{ "triangle_strip", scene::layer_item::shape },
{ "triangle_fan", scene::layer_item::shape },
{ "lines", scene::layer_item::lines },
{ "line_strip", scene::layer_item::lines },
{ "line_loop", scene::layer_item::lines },
{ "memcell", scene::layer_item::memcell },
{ "eventlauncher", scene::layer_item::launcher },
{ "sound", scene::layer_item::sound } };
auto const lookup { itemtypes.find( nodedata.type ) };
if( lookup != itemtypes.end() ) {
scene::Layers.count(
nodedata.layer,
// models with negative minimum range are 3d terrain, converted to shapes
( lookup->second == scene::layer_item::model && nodedata.range_min < 0.0 ) ?
scene::layer_item::shape :
lookup->second );
}
}
} }
void void
@@ -1328,6 +1368,7 @@ TAnimModel *state_serializer::create_model(const std::string &src, const std::st
parser.getTokens(2); // name, type parser.getTokens(2); // name, type
nodedata.name = name; nodedata.name = name;
nodedata.type = "model"; nodedata.type = "model";
nodedata.layer = scene::Layers.active(); // null_handle unless the scenery was opened for editing
scene::scratch_data scratch; scene::scratch_data scratch;
@@ -1340,6 +1381,7 @@ TAnimModel *state_serializer::create_model(const std::string &src, const std::st
cloned->location(position); cloned->location(position);
simulation::Instances.insert(cloned); simulation::Instances.insert(cloned);
simulation::Region->insert(cloned); simulation::Region->insert(cloned);
scene::Layers.count(cloned->layer(), scene::layer_item::model);
return cloned; return cloned;
} }

View File

@@ -56,6 +56,7 @@ namespace scene {
struct node_data; struct node_data;
class basic_node; class basic_node;
using group_handle = std::size_t; using group_handle = std::size_t;
using layer_handle = std::uint16_t;
} }
namespace Mtable namespace Mtable

View File

@@ -84,6 +84,7 @@ struct global_settings {
std::string szDefaultExt{ szTexturesDDS }; std::string szDefaultExt{ szTexturesDDS };
std::string SceneryFile; std::string SceneryFile;
std::string local_start_vehicle{ "EU07-424" }; std::string local_start_vehicle{ "EU07-424" };
bool editor_session{ false }; // scenery was opened for editing (-edit): loader keeps track of scenery layers and hands over to the editor
int iConvertModels{ 0 }; // tworzenie plików binarnych int iConvertModels{ 0 }; // tworzenie plików binarnych
int iConvertIndexRange{ 1000 }; // range of duplicate vertex scan int iConvertIndexRange{ 1000 }; // range of duplicate vertex scan
bool file_binary_terrain{ true }; // enable binary terrain (de)serialization bool file_binary_terrain{ true }; // enable binary terrain (de)serialization

View File

@@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Logs.h" #include "utilities/Logs.h"
#include "scene/scenenodegroups.h" #include "scene/scenenodegroups.h"
#include "scene/scenelayers.h"
/* /*
MaSzyna EU07 locomotive simulator parser MaSzyna EU07 locomotive simulator parser
@@ -118,6 +119,11 @@ cParser::~cParser()
// wrap up the node group holding content of processed file // wrap up the node group holding content of processed file
scene::Groups.close(); scene::Groups.close();
} }
if (true == mLayerFile)
{
// content of the processed file ended, following nodes belong to the parent layer again
scene::Layers.close();
}
} }
template <> glm::vec3 cParser::getToken(bool const ToLower, char const *Break) template <> glm::vec3 cParser::getToken(bool const ToLower, char const *Break)
@@ -336,6 +342,12 @@ void cParser::startIncludeFromParser(cParser& srcParser, bool ToLower, std::stri
if (isTerrain && true == Global.file_binary_terrain_state) { if (isTerrain && true == Global.file_binary_terrain_state) {
WriteLog("SBT found, ignoring: " + includefile); WriteLog("SBT found, ignoring: " + includefile);
readParameters(srcParser); // preserve original side-effect: still consume parameters readParameters(srcParser); // preserve original side-effect: still consume parameters
if (sceneryLayers)
{
// the file is still a part of the scenery, even though its content comes from the binary terrain file
scene::Layers.layer(scene::Layers.open(includefile)).binary = true;
scene::Layers.close();
}
return; return;
} }
@@ -355,6 +367,18 @@ void cParser::startIncludeFromParser(cParser& srcParser, bool ToLower, std::stri
if (mIncludeParser->mSize <= 0) { if (mIncludeParser->mSize <= 0) {
ErrorLog("Bad include: can't open file \"" + includefile + "\""); ErrorLog("Bad include: can't open file \"" + includefile + "\"");
} }
if (sceneryLayers)
{
mIncludeParser->sceneryLayers = true;
// content of an included scenery file forms a layer of its own. *.inc files are node templates
// reused all over the scenery, so their content stays in the layer of the file which includes them
if (false == mIncludeParser->mIncFile && mIncludeParser->mSize > 0)
{
scene::Layers.open(includefile);
mIncludeParser->mLayerFile = true;
}
}
} }
bool cParser::handleIncludeIfPresent(std::string& token, bool ToLower, const char* Break) { bool cParser::handleIncludeIfPresent(std::string& token, bool ToLower, const char* Break) {
@@ -524,6 +548,7 @@ void cParser::injectString(const std::string &str)
{ {
mIncludeParser = std::make_shared<cParser>(str, buffer_TEXT, "", LoadTraction, std::vector<std::string>(), allowRandomIncludes); mIncludeParser = std::make_shared<cParser>(str, buffer_TEXT, "", LoadTraction, std::vector<std::string>(), allowRandomIncludes);
mIncludeParser->autoclear(m_autoclear); mIncludeParser->autoclear(m_autoclear);
mIncludeParser->sceneryLayers = sceneryLayers;
} }
} }

View File

@@ -97,6 +97,7 @@ class cParser //: public std::stringstream
bool expandIncludes = true; bool expandIncludes = true;
bool allowRandomIncludes = false; bool allowRandomIncludes = false;
bool skipComments = true; bool skipComments = true;
bool sceneryLayers = false; // included scenery files are registered as scenery layers (scenery opened for editing)
private: private:
void startIncludeFromParser(cParser &srcParser, bool ToLower, std::string includefile); void startIncludeFromParser(cParser &srcParser, bool ToLower, std::string includefile);
@@ -118,6 +119,7 @@ class cParser //: public std::stringstream
std::streamoff mSize { 0 }; // size of open stream, for progress report. std::streamoff mSize { 0 }; // size of open stream, for progress report.
std::size_t mLine { 0 }; // currently processed line std::size_t mLine { 0 }; // currently processed line
bool mIncFile { false }; // the parser is processing an *.inc file bool mIncFile { false }; // the parser is processing an *.inc file
bool mLayerFile { false }; // the parser is processing a file registered as scenery layer
bool mFirstToken { true }; // processing first token in the current file; helper used when checking for utf bom bool mFirstToken { true }; // processing first token in the current file; helper used when checking for utf bom
typedef std::map<std::string, std::string> commentmap; typedef std::map<std::string, std::string> commentmap;
commentmap mComments { commentmap mComments {