17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 00:29:19 +02:00
Files
maszyna/scene/scenelayers.h
Hirek193 8cd0bab27b Editor terrain: texture browser, undo, conversion from terrain files
- Terrain work mode shows a browser of the textures folder in place of the
  node bank; textures/materials can be dragged onto the palette entries
  (replacing the material), the material field and the water material field
- Ctrl+Z/Ctrl+Y for heightmap terrain changes: a brush stroke or an
  operation is one step of the editor history
- Ground conversion reads only the files of the terrain directives
  (.txtf with origin/rotate, or .btf without text), paints the chunks with
  the materials of the triangles, drops only the shapes of those files
- Fix: ground grids converted out-of-range floats to int, so whole-scene
  queries returned the triangles of a single cell (shifted chunks)
- Fix: terrain layers copied from textures already uploaded without their
  data stayed grey
- Fix: ImGui ID conflict between terrain tool radios and section headers
- Fix: layers of terrain .txtf files named with the scenery/ prefix and
  could not be saved
- Editor selftest commands for the terrain
2026-10-09 23:39:26 +02:00

396 lines
18 KiB
C++

/*
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"
#include <filesystem>
#include <limits>
#include <map>
#include <set>
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_
};
// placement applied to scenery statements by the origin, rotate and scale directives in effect
struct layer_context
{
glm::dvec3 offset{0.0};
glm::vec3 rotation{0.f};
glm::vec3 scale{1.f};
// converts world space location to the one which produces it when loaded with this context in effect
glm::dvec3 to_local(glm::dvec3 Location) const;
// reverse of to_local()
glm::dvec3 to_world(glm::dvec3 Location) const;
bool matches(layer_context const &Other) const;
};
// location of a piece of text in a scenery file, as byte offsets
struct source_span
{
std::streamoff begin{-1};
std::streamoff end{-1};
bool valid() const
{
return begin >= 0 && end > begin;
}
};
// include directive bringing a scenery file into another one
struct include_site
{
layer_handle parent{null_handle}; // layer of the file holding the directive
source_span span; // location of the whole directive in that file
bool parameters{false}; // the file is included with parameters
bool fixed{false}; // the directive can't be rewritten: it's a part of an *.inc template, or its location is unknown
};
// 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
// source data, used by the scenery save
std::vector<include_site> sites; // include directives bringing the file in; empty for the scenario file
layer_context context_begin; // placement in effect at the start of the file...
layer_context context_end; // ...and right after its end
// location in the file of what performs the scenario initialization, if the file takes a part in it: the FirstInit
// directive, or the directive including the file which holds it. only vehicles are meant to follow the
// initialization, so what the editor adds to the file goes ahead of this point rather than at the end
source_span init;
layer_context context_init; // placement in effect at that point
bool late{false}; // the file is loaded after the scenario initialization
std::uintmax_t filesize{0}; // state of the file when it was loaded or saved the last time
std::filesystem::file_time_type filetime{};
// pending changes, applied by the scenery save
bool created{false}; // layer was created in the editor, its file doesn't exist yet
bool editormade{false}; // the file was made by the editor during this session, there's no original content to keep a backup of
layer_handle anchor{null_handle}; // for created layer: sibling its include directive goes after
bool removed{false}; // layer is to be dropped from the scenery
layer_handle merged{null_handle}; // layer which took over the content of this one
bool dead{false}; // removal or merge of the layer was saved, it's no longer a part of the scenery
std::size_t item_count() const;
// placement in effect at the point of the file which receives the content added by the editor
layer_context const &context_insert() const
{
return init.valid() ? context_init : context_end;
}
};
// include directive bringing an *.inc template into a scenery layer file
struct include_instance
{
layer_handle layer{null_handle}; // layer of the file holding the directive
std::string const *file{nullptr}; // name of the included template
source_span span; // location of the whole directive in the file; invalid for directive which wasn't saved yet
std::string directive; // text of the directive, for include placed or changed in the editor and not saved yet
layer_context context; // placement in effect at the directive
bool removed{false}; // the include is to be dropped from the scenery
bool dead{false}; // removal of the include was saved
bool rebuilt{false}; // models the include was loaded with gave way to the ones made by the editor
};
// marks nodes defined by a template which was included in a way the editor doesn't keep track of
instance_handle const untracked_instance{std::numeric_limits<instance_handle>::max()};
// origin of a node defined in a scenery layer file, with the placement it was loaded with
struct node_source
{
layer_handle layer{null_handle}; // layer of the file holding the node definition
source_span span; // location of the definition in that file
layer_context context; // placement in effect at the definition
glm::dvec3 location{0.0};
glm::vec3 angles{0.f};
glm::vec3 scale{1.f};
};
// definition of a shape (triangles) in a scenery layer file
struct shape_source
{
layer_handle layer{null_handle};
source_span span;
material_handle material{null_handle};
bool erased{false}; // the definition goes from the file on save
};
struct save_result
{
bool success{false};
std::vector<std::string> files; // names of saved files
std::vector<std::string> backups; // copies of the files made before they lost their ground
std::string message; // summary, or cause of the failure
};
// 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 the scenario file. returns: handle to its layer
layer_handle open(std::string const &Name);
// indicates start of content of specified scenery file, included by specified directive. returns: handle to the layer of the file
layer_handle open(std::string const &Name, include_site Site);
// 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];
}
// follows layer merges. returns: handle of the layer which currently holds content of specified layer
layer_handle resolve(layer_handle Layer) const;
// true if the layer is a part of the scenery and should be presented to the user
bool listed(layer_handle const Layer) const
{
return valid(Layer) && false == layer(Layer).dead && layer(Layer).merged == null_handle;
}
// number of layers between specified layer and the scenario file
int depth(layer_handle Layer) const;
// handles of the layers to present to the user, each followed by the layers it includes
std::vector<layer_handle> tree() const;
// adjusts item count of specified layer
void count(layer_handle 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 Layer);
// shows or hides nodes of specified layer. visibility defined in the scenery is preserved, hidden layer only overrides it
void visible(layer_handle Layer, bool const Visible);
void locked(layer_handle Layer, bool const Locked);
// true if the file of specified layer can be rewritten with changed nodes. optionally explains why it can't
bool writable(layer_handle Layer, std::string *Reason = nullptr) const;
// true if nodes of specified layer can be selected and modified in the editor
bool editable(layer_handle Layer) const;
// true if specified node can be selected and modified in the editor. optionally explains why it can't
bool editable(basic_node const *Node, std::string *Reason = nullptr) const;
// moves specified node to specified layer
void move(basic_node *Node, layer_handle Layer);
// handles of layers which are currently hidden
std::vector<layer_handle> hidden() const;
// restores visibility of all layers
void show_all();
// scenery load bookkeeping
// registers *.inc template used by the scenery
void template_used(std::string const &Name)
{
m_templates.emplace(Name);
}
std::set<std::string> const &templates() const
{
return m_templates;
}
// indicates start of content of a template, included by directive at specified location of the layer file being loaded
void instance_begin(std::string const &File, source_span const &Span);
// indicates end of content of the template
void instance_end()
{
m_instance = 0;
}
// include whose template is being loaded, 0 if there's none
instance_handle instance() const
{
return m_instance;
}
// updates placement in effect for the statements which follow
void context(layer_context const &Context)
{
m_context = Context;
}
// stores origin of a node defined directly in a layer file, to allow rewriting its definition on save
void track(basic_node const *Node, source_span const &Span, glm::vec3 const &Angles = glm::vec3{0.f}, glm::vec3 const &Scale = glm::vec3{1.f});
// indicates specified node was removed from the scene
void forget(basic_node const *Node);
// indicates specified node was given another name in the editor, for its definition to get it on save
void renamed(basic_node const *Node);
// stores location of a shape defined directly in the layer file being loaded
void shape(material_handle const Material, source_span const &Span);
std::vector<shape_source> const &shapes() const
{
return m_shapes;
}
// drops the definitions of the shapes of specified materials from the writable layer files on save; only from specified layers, if given.
// returns: amount of the shapes to drop, and of the ones which stay as their files can't be rewritten
std::pair<std::size_t, std::size_t> erase_shapes(std::set<material_handle> const &Materials, std::set<layer_handle> const *Layers = nullptr);
// indicates the scenario initialization (FirstInit) is being performed. Span: location of the directive.
// Infile: the directive comes straight from the layer file being loaded
void initialization(source_span const &Span, bool Infile);
// true if specified layer can receive content added in the editor. optionally explains why it can't
bool accepts(layer_handle Layer, std::string *Reason = nullptr) const;
// notes the scenery already holds the directive setting up terrain made in the editor
void terrain_directive(bool const Present)
{
m_terraindirective = Present;
}
bool terrain_directive() const
{
return m_terraindirective;
}
// editor data kept in the scenery files as comment lines starting with a mark, e.g. "//$p ...", which the simulation skips
// text of the lines with specified mark in specified layer, with the mark taken off
std::vector<std::string> marked(layer_handle Layer, std::string const &Mark) const;
// layers whose files hold lines with specified mark
std::vector<layer_handle> marked_layers(std::string const &Mark) const;
// lines to take the place of the ones with specified mark in the layer file, on save. the mark is added to each
void mark(layer_handle Layer, std::string const &Mark, std::vector<std::string> Lines);
// layer management. changes are applied to the scenery files by save()
// creates empty layer, included from specified one. returns: handle to the new layer, or null_handle with the explanation in Reason
layer_handle create(std::string Name, layer_handle Parent, std::string &Reason);
bool can_remove(layer_handle Layer, std::string &Reason) const;
// marks specified layer and the layers it includes as dropped from the scenery. their files are left on the disk
bool remove(layer_handle Layer);
// takes back remove() which wasn't saved yet
void restore(layer_handle Layer);
bool can_merge(layer_handle Source, layer_handle Target, std::string &Reason) const;
// moves content of the source layer to the target layer
bool merge(layer_handle Source, layer_handle Target);
// true if there are layer changes awaiting save. NOTE: doesn't account for modified nodes
bool pending() const;
// includes of *.inc templates. the editor handles each as a whole: it can be placed and removed, but not taken apart
// true if specified handle stands for an include the editor keeps track of
bool tracked(instance_handle const Instance) const
{
return Instance != 0 && Instance <= m_instances.size();
}
// handles of the includes run from 1 up to this number
std::size_t instance_count() const
{
return m_instances.size();
}
// grants access to specified include. NOTE: the include has to be tracked
include_instance const &instance(instance_handle const Instance) const
{
return m_instances[Instance - 1];
}
// true if specified include can be removed in the editor. optionally explains why it can't
bool removable(instance_handle Instance, std::string *Reason = nullptr) const;
// drops specified include from the scenery, or brings it back. its nodes are hidden right away, the directive
// is erased from the layer file on save; what else the template defines stays in the scene until the next load
void removed(instance_handle Instance, bool Removed);
// registers include directive made in the editor, to be written to the file of specified layer on save.
// the directive has to be prepared for the placement in effect at the end of that file. returns: handle to the include
instance_handle place(layer_handle Layer, std::string const &File, std::string const &Directive);
// text of the directive of specified include, as changed in the editor or as it stands in the scenery file.
// returns: empty text if the directive can't be read
std::string directive(instance_handle Instance) const;
// replaces the directive of specified include, the scenery file receives it on save. NOTE: the nodes aren't touched
bool modify(instance_handle Instance, std::string const &Directive);
// takes the models specified include was loaded with out of the scene, once the editor shows its own in their place
void rebuilt(instance_handle Instance);
// true if specified node was replaced by the editor with a node showing the current state of its include
bool stale(basic_node const *Node) const
{
return tracked(Node->m_instance) && instance(Node->m_instance).rebuilt && false == Node->m_preview;
}
// true if specified layer is included, directly or not, by the other one
bool is_descendant(layer_handle Layer, layer_handle const Ancestor) const;
// writes changes made in the editor to the scenery files: changed, created and deleted model instances and
// memory cells, and layer changes. the files are left intact unless the whole operation can be performed.
// Rootstatements: additional statements to place at the end of the scenario file
save_result save(std::vector<std::string> const &Rootstatements = {}, std::vector<std::string> const &Trailingstatements = {});
std::string check_sources() const;
private:
// types
struct file_patch;
struct composition;
struct save_state;
struct marked_line
{
std::string mark;
source_span span; // whole line, its end included
std::string text;
};
// methods
std::string path(layer_handle const Layer) const;
void stat(layer_handle const Layer);
// finds the marked lines in the layer file
void scan(layer_handle const Layer);
bool compose(save_state &State, layer_handle const Layer, composition &Output, int const Depth) const;
// 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};
layer_context m_context; // placement in effect at the current point of scenery load
std::unordered_map<basic_node const *, node_source> m_sources; // origins of nodes which can be rewritten on save
std::vector<std::pair<layer_handle, source_span>> m_erased; // definitions of nodes deleted since the load or the last save
std::unordered_set<basic_node const *> m_renamed; // nodes given another name since the load or the last save
std::vector<shape_source> m_shapes;
bool m_shapeserased{false}; // the binary terrain file holds shapes which the save drops
std::set<std::string> m_templates; // *.inc templates used by the scenery
std::vector<include_instance> m_instances; // includes of the templates; instance handle is index in the vector + 1
instance_handle m_instance{0}; // helper, include whose template is being loaded
bool m_initialized{false}; // helper, the scenario initialization was performed already
bool m_terraindirective{false};
std::map<layer_handle, std::vector<marked_line>> m_marked;
std::map<std::pair<layer_handle, std::string>, std::vector<std::string>> m_marking; // changes awaiting save
};
extern node_layers Layers;
} // namespace scene
//---------------------------------------------------------------------------