diff --git a/CMakeLists.txt b/CMakeLists.txt index 1485e520..13990261 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -178,6 +178,7 @@ set(SOURCES "application/scenarioloadermode.cpp" "application/scenarioloaderuilayer.cpp" "scene/scenenodegroups.cpp" +"scene/scenelayers.cpp" "simulation/simulationenvironment.cpp" "simulation/simulationstateserializer.cpp" "rendering/precipitation.cpp" diff --git a/application/application.cpp b/application/application.cpp index ceb5ec6c..db77c976 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -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; } diff --git a/application/drivermode.cpp b/application/drivermode.cpp index 80fd3b17..dbe3e176 100644 --- a/application/drivermode.cpp +++ b/application/drivermode.cpp @@ -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 diff --git a/application/drivermode.h b/application/drivermode.h index 66ff6936..e0406043 100644 --- a/application/drivermode.h +++ b/application/drivermode.h @@ -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 }; diff --git a/application/editormode.cpp b/application/editormode.cpp index 74594118..2dd32555 100644 --- a/application/editormode.cpp +++ b/application/editormode.cpp @@ -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}); diff --git a/application/editormode.h b/application/editormode.h index c251186a..550d05a2 100644 --- a/application/editormode.h +++ b/application/editormode.h @@ -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 diff --git a/application/editoruilayer.cpp b/application/editoruilayer.cpp index 17514efb..99423d60 100644 --- a/application/editoruilayer.cpp +++ b/application/editoruilayer.cpp @@ -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 "); + return; + } + + ImGui::TextDisabled("visible, locked, layer file (click: make active)"); + + std::pair 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(idx)}; + auto const &layer{scene::Layers.layer(handle)}; + auto const isactive{handle == scene::Layers.active()}; + + ImGui::PushID(static_cast(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(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() diff --git a/application/editoruilayer.h b/application/editoruilayer.h index 8b15d35d..68808d2e 100644 --- a/application/editoruilayer.h +++ b/application/editoruilayer.h @@ -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 diff --git a/application/editoruipanels.cpp b/application/editoruipanels.cpp index 6c9a2df9..41849a8a 100644 --- a/application/editoruipanels.cpp +++ b/application/editoruipanels.cpp @@ -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 diff --git a/scene/scenelayers.cpp b/scene/scenelayers.cpp new file mode 100644 index 00000000..3acb0154 --- /dev/null +++ b/scene/scenelayers.cpp @@ -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(std::distance(std::begin(m_layers), lookup) + 1)}; + if (lookup == std::end(m_layers)) + { + if (m_layers.size() < static_cast(std::numeric_limits::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(Item)]}; + if (Change >= 0) + { + itemcount += static_cast(Change); + } + else + { + itemcount -= std::min(itemcount, static_cast(-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 node_layers::hidden() const +{ + std::vector layers; + for (std::size_t idx = 0; idx < m_layers.size(); ++idx) + { + if (false == m_layers[idx].visible) + { + layers.emplace_back(static_cast(idx + 1)); + } + } + return layers; +} + +void node_layers::show_all() +{ + for (auto const hiddenlayer : hidden()) + { + visible(hiddenlayer, true); + } +} + +} // namespace scene + +//--------------------------------------------------------------------------- diff --git a/scene/scenelayers.h b/scene/scenelayers.h new file mode 100644 index 00000000..71df3726 --- /dev/null +++ b/scene/scenelayers.h @@ -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(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 hidden() const; + // restores visibility of all layers + void show_all(); + + private: + // members + std::vector m_layers; // layer handle is index in the vector + 1 + std::vector m_stack; // helper, layers of scenery files being loaded + layer_handle m_active{null_handle}; +}; + +extern node_layers Layers; + +} // namespace scene + +//--------------------------------------------------------------------------- diff --git a/scene/scenenode.cpp b/scene/scenenode.cpp index 1ba822e1..41b035bd 100644 --- a/scene/scenenode.cpp +++ b/scene/scenenode.cpp @@ -717,7 +717,8 @@ memory_node::deserialize( cParser &Input, 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(); node_type = Nodedata.type; diff --git a/scene/scenenode.h b/scene/scenenode.h index ca727c2c..533ed41a 100644 --- a/scene/scenenode.h +++ b/scene/scenenode.h @@ -70,6 +70,7 @@ struct node_data { double range_max { std::numeric_limits::max() }; std::string name; 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 @@ -359,6 +360,10 @@ public: group( scene::group_handle Group ); scene::group_handle group() const; + void + layer( scene::layer_handle Layer ); + scene::layer_handle + layer() const; void mark_dirty() { m_dirty = true; } bool @@ -375,6 +380,8 @@ public: bool m_visible { true }; // visibility flag std::string m_name; 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; private: @@ -443,6 +450,18 @@ basic_node::group() const { 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 //--------------------------------------------------------------------------- diff --git a/simulation/simulation.cpp b/simulation/simulation.cpp index a1493f63..e10b0843 100644 --- a/simulation/simulation.cpp +++ b/simulation/simulation.cpp @@ -24,6 +24,7 @@ http://mozilla.org/MPL/2.0/. #include "rendering/lightarray.h" #include "rendering/particles.h" #include "scene/scene.h" +#include "scene/scenelayers.h" #include "vehicle/Train.h" #include "application/application.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) { + scene::Layers.count(model->layer(), scene::layer_item::model, -1); Region->erase(model); Instances.purge(model); } diff --git a/simulation/simulationstateserializer.cpp b/simulation/simulationstateserializer.cpp index 99a733d3..f7429b1d 100644 --- a/simulation/simulationstateserializer.cpp +++ b/simulation/simulationstateserializer.cpp @@ -16,6 +16,7 @@ http://mozilla.org/MPL/2.0/. #include "simulation/simulationsounds.h" #include "simulation/simulationenvironment.h" #include "scene/scenenodegroups.h" +#include "scene/scenelayers.h" #include "rendering/particles.h" #include "world/Event.h" #include "world/MemCell.h" @@ -73,6 +74,14 @@ state_serializer::deserialize_begin( std::string const &Scenariofile ) { } 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() ) throw invalid_scenery_exception(); @@ -155,6 +164,7 @@ state_serializer::deserialize_continue(std::shared_ptr state } scene::Groups.close(); + scene::Layers.close(); scene::Groups.update_map(); Region->create_map_geometry(); @@ -345,6 +355,7 @@ state_serializer::deserialize_event( cParser &Input, scene::scratch_data &Scratc if( true == simulation::Events.insert( event ) ) { scene::Groups.insert( scene::Groups.handle(), event ); + scene::Layers.count( scene::Layers.handle(), scene::layer_item::event ); } else { delete event; @@ -436,6 +447,7 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch >> nodedata.name >> nodedata.type; if( nodedata.name == "none" ) { nodedata.name.clear(); } + nodedata.layer = scene::Layers.handle(); // type-based deserialization. not elegant but it'll do if( nodedata.type == "dynamic" ) { @@ -643,6 +655,34 @@ state_serializer::deserialize_node( cParser &Input, scene::scratch_data &Scratch 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 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 @@ -1328,6 +1368,7 @@ TAnimModel *state_serializer::create_model(const std::string &src, const std::st parser.getTokens(2); // name, type nodedata.name = name; nodedata.type = "model"; + nodedata.layer = scene::Layers.active(); // null_handle unless the scenery was opened for editing scene::scratch_data scratch; @@ -1340,6 +1381,7 @@ TAnimModel *state_serializer::create_model(const std::string &src, const std::st cloned->location(position); simulation::Instances.insert(cloned); simulation::Region->insert(cloned); + scene::Layers.count(cloned->layer(), scene::layer_item::model); return cloned; } diff --git a/utilities/Classes.h b/utilities/Classes.h index f79cb851..568e3ae9 100644 --- a/utilities/Classes.h +++ b/utilities/Classes.h @@ -56,6 +56,7 @@ namespace scene { struct node_data; class basic_node; using group_handle = std::size_t; +using layer_handle = std::uint16_t; } namespace Mtable diff --git a/utilities/Globals.h b/utilities/Globals.h index d0f70701..800737f6 100644 --- a/utilities/Globals.h +++ b/utilities/Globals.h @@ -84,6 +84,7 @@ struct global_settings { std::string szDefaultExt{ szTexturesDDS }; std::string SceneryFile; 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 iConvertIndexRange{ 1000 }; // range of duplicate vertex scan bool file_binary_terrain{ true }; // enable binary terrain (de)serialization diff --git a/utilities/parser.cpp b/utilities/parser.cpp index 7209df01..4219765c 100644 --- a/utilities/parser.cpp +++ b/utilities/parser.cpp @@ -12,6 +12,7 @@ http://mozilla.org/MPL/2.0/. #include "utilities/Logs.h" #include "scene/scenenodegroups.h" +#include "scene/scenelayers.h" /* MaSzyna EU07 locomotive simulator parser @@ -118,6 +119,11 @@ cParser::~cParser() // wrap up the node group holding content of processed file 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) @@ -336,6 +342,12 @@ void cParser::startIncludeFromParser(cParser& srcParser, bool ToLower, std::stri if (isTerrain && true == Global.file_binary_terrain_state) { WriteLog("SBT found, ignoring: " + includefile); 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; } @@ -355,6 +367,18 @@ void cParser::startIncludeFromParser(cParser& srcParser, bool ToLower, std::stri if (mIncludeParser->mSize <= 0) { 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) { @@ -524,6 +548,7 @@ void cParser::injectString(const std::string &str) { mIncludeParser = std::make_shared(str, buffer_TEXT, "", LoadTraction, std::vector(), allowRandomIncludes); mIncludeParser->autoclear(m_autoclear); + mIncludeParser->sceneryLayers = sceneryLayers; } } diff --git a/utilities/parser.h b/utilities/parser.h index 0cf51d8c..1a806582 100644 --- a/utilities/parser.h +++ b/utilities/parser.h @@ -97,6 +97,7 @@ class cParser //: public std::stringstream bool expandIncludes = true; bool allowRandomIncludes = false; bool skipComments = true; + bool sceneryLayers = false; // included scenery files are registered as scenery layers (scenery opened for editing) private: 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::size_t mLine { 0 }; // currently processed line 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 typedef std::map commentmap; commentmap mComments {