17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-05 10:59:02 +01:00

feat(editor): vertical profile editor, grade lines kept in the scenery

This commit is contained in:
maj00r
2026-10-04 00:14:03 +02:00
parent cdd5c242d1
commit 7371b18910
14 changed files with 2591 additions and 76 deletions

View File

@@ -17,7 +17,10 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Globals.h"
#include "utilities/utilities.h"
#include <array>
#include <fstream>
#include <functional>
#include <iterator>
namespace scene
{
@@ -37,6 +40,17 @@ glm::dvec3 layer_context::to_local(glm::dvec3 Location) const
return Location;
}
glm::dvec3 layer_context::to_world(glm::dvec3 Location) const
{
if (rotation != glm::vec3{0.f})
{
Location = glm::rotateY<double>(Location, glm::radians(static_cast<double>(rotation.y)));
}
Location *= glm::dvec3{scale};
Location += offset;
return Location;
}
bool layer_context::matches(layer_context const &Other) const
{
return glm::all(glm::epsilonEqual(offset, Other.offset, 1e-4)) && glm::all(glm::epsilonEqual(rotation, Other.rotation, 1e-4f)) && glm::all(glm::epsilonEqual(scale, Other.scale, 1e-5f));
@@ -62,6 +76,7 @@ layer_handle node_layers::open(std::string const &Name)
m_layers.back().parent = handle();
m_layers.back().late = m_initialized;
stat(layerhandle);
scan(layerhandle);
}
else
{
@@ -119,6 +134,8 @@ void node_layers::clear()
m_instance = 0;
m_initialized = false;
m_terraindirective = false;
m_marked.clear();
m_marking.clear();
}
// indicates the scenario initialization (FirstInit) is being performed
@@ -767,7 +784,104 @@ bool node_layers::merge(layer_handle Source, layer_handle Target)
// true if there are layer changes awaiting save. NOTE: doesn't account for modified nodes
bool node_layers::pending() const
{
return std::any_of(std::begin(m_layers), std::end(m_layers), [](basic_layer const &Layer) { return false == Layer.dead && (Layer.created || Layer.removed || Layer.merged != null_handle); });
return false == m_marking.empty() ||
std::any_of(std::begin(m_layers), std::end(m_layers), [](basic_layer const &Layer) { return false == Layer.dead && (Layer.created || Layer.removed || Layer.merged != null_handle); });
}
namespace
{
// marks of the lines the editor keeps track of
std::array<char const *, 2> const editormarks{"//$p", "//$b"};
} // namespace
void node_layers::scan(layer_handle const Layer)
{
m_marked.erase(Layer);
std::ifstream file{path(Layer), std::ios_base::binary};
if (false == file.is_open())
{
return;
}
std::string content{std::istreambuf_iterator<char>(file), std::istreambuf_iterator<char>()};
if (content.find("//$") == std::string::npos)
{
return;
}
std::size_t begin{0};
while (begin < content.size())
{
auto end{content.find('\n', begin)};
end = (end == std::string::npos ? content.size() : end + 1);
auto const first{content.find_first_not_of(" \t", begin)};
if (first < end)
{
for (auto const *mark : editormarks)
{
auto const length{std::char_traits<char>::length(mark)};
if (content.compare(first, length, mark) != 0 || (first + length < end && content[first + length] != ' ' && content[first + length] != '\t' && content[first + length] != '\r' && content[first + length] != '\n'))
{
continue;
}
auto text{content.substr(first + length, end - first - length)};
while (false == text.empty() && (text.back() == '\n' || text.back() == '\r'))
{
text.pop_back();
}
text.erase(0, text.find_first_not_of(" \t"));
m_marked[Layer].push_back({mark, {static_cast<std::streamoff>(begin), static_cast<std::streamoff>(end)}, std::move(text)});
break;
}
}
begin = end;
}
}
std::vector<std::string> node_layers::marked(layer_handle const Layer, std::string const &Mark) const
{
std::vector<std::string> result;
if (auto const pending{m_marking.find({Layer, Mark})}; pending != m_marking.end())
{
return pending->second;
}
if (auto const lookup{m_marked.find(Layer)}; lookup != m_marked.end())
{
for (auto const &line : lookup->second)
{
if (line.mark == Mark)
{
result.push_back(line.text);
}
}
}
return result;
}
std::vector<layer_handle> node_layers::marked_layers(std::string const &Mark) const
{
std::vector<layer_handle> result;
for (auto const &entry : m_marked)
{
if (listed(entry.first) && std::any_of(entry.second.begin(), entry.second.end(), [&](marked_line const &Line) { return Line.mark == Mark; }))
{
result.push_back(entry.first);
}
}
for (auto const &entry : m_marking)
{
if (entry.first.second == Mark && std::find(result.begin(), result.end(), entry.first.first) == result.end())
{
result.push_back(entry.first.first);
}
}
return result;
}
void node_layers::mark(layer_handle const Layer, std::string const &Mark, std::vector<std::string> Lines)
{
if (valid(Layer))
{
m_marking[{Layer, Mark}] = std::move(Lines);
}
}
// indicates start of content of a template, included by directive at specified location of the layer file being loaded

View File

@@ -13,6 +13,7 @@ http://mozilla.org/MPL/2.0/.
#include <filesystem>
#include <limits>
#include <map>
#include <set>
namespace scene
@@ -44,6 +45,8 @@ struct layer_context
// 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;
};
@@ -261,6 +264,14 @@ class node_layers
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);
@@ -319,9 +330,17 @@ class node_layers
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
@@ -335,6 +354,8 @@ class node_layers
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;

View File

@@ -801,6 +801,37 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
state.patches[erased.first].edits.emplace_back(std::move(edit));
}
// editor data in the comment lines: the old lines go, the new ones are added to the file like new definitions
std::map<layer_handle, std::vector<std::string>> markedtext;
for (auto const &marking : m_marking)
{
auto const markedlayer{marking.first.first};
auto const &mark{marking.first.second};
if (auto const lookup{m_marked.find(markedlayer)}; lookup != m_marked.end())
{
for (auto const &line : lookup->second)
{
if (line.mark != mark)
{
continue;
}
file_patch::edit edit;
edit.begin = line.span.begin;
edit.end = line.span.end;
state.patches[markedlayer].edits.emplace_back(std::move(edit));
}
}
auto const target{resolve(markedlayer)};
if (marking.second.empty() || false == is_output(target))
{
continue;
}
for (auto const &text : marking.second)
{
markedtext[target].push_back(mark + ' ' + text);
}
}
// includes of templates
std::map<layer_handle, std::vector<instance_handle>> placed; // directives made in the editor, by target layer
for (std::size_t idx = 0; idx < m_instances.size(); ++idx)
@@ -940,7 +971,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
continue;
}
if (layer(candidate).created || state.patches.count(candidate) != 0 || appended.count(candidate) != 0 || created.count(candidate) != 0 || placed.count(candidate) != 0 ||
(candidate == root && false == Rootstatements.empty()))
markedtext.count(candidate) != 0 || (candidate == root && false == Rootstatements.empty()))
{
outputs.emplace_back(candidate);
}
@@ -1034,6 +1065,14 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
added.text += directive + eol;
}
}
if (auto const lookup{markedtext.find(output)}; lookup != markedtext.end())
{
for (auto const &line : lookup->second)
{
ensure_newline(added.text, eol);
added.text += line + eol;
}
}
if (output == root)
{
for (auto const &statement : Rootstatements)
@@ -1221,7 +1260,17 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements)
for (auto const output : written)
{
stat(output);
scan(output);
}
for (auto const &marking : m_marking)
{
if (resolve(marking.first.first) != marking.first.first)
{
// the lines of the merged layer are now in the file of its target
m_marked.erase(marking.first.first);
}
}
m_marking.clear();
for (auto &included : m_instances)
{
// removed includes are gone from the files now