17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-01 18:24:21 +01:00

Merge remote-tracking branch 'origin/feat/switch-blades' into feat/gl46-migration

This commit is contained in:
2026-10-19 22:13:43 +02:00
169 changed files with 52371 additions and 10816 deletions

View File

@@ -483,8 +483,17 @@ bool modifier_shape::reaches(glm::dvec2 const &Min, glm::dvec2 const &Max) const
bool modifier_shape::apply(double const X, double const Z, float &Height) const
{
if (m_grid.empty() || X < m_min.x || X > m_max.x || Z < m_min.y || Z > m_max.y)
auto const result{evaluate(X, Z, Height)};
if (false == result.has_value() || std::abs(result->height - Height) < 0.001)
return false;
Height = static_cast<float>(result->height);
return true;
}
std::optional<modifier_shape::effect> modifier_shape::evaluate(double const X, double const Z, double const Ground) const
{
if (m_grid.empty() || X < m_min.x || X > m_max.x || Z < m_min.y || Z > m_max.y)
return std::nullopt;
auto const &points{m_modifier.points};
auto const reach{m_widest + m_modifier.reach};
auto distance{reach};
@@ -517,18 +526,17 @@ bool modifier_shape::apply(double const X, double const Z, float &Height) const
}
}
if (false == found)
return false;
return std::nullopt;
// the formation under the axis, slopes from its edges down or up to the ground, the bends rounded off
auto const slope{std::max(0.1, m_modifier.slope)};
auto const rounding{std::max(0.0, m_modifier.rounding)};
auto const ground{static_cast<double>(Height)};
auto const ground{Ground};
auto const beyond{soft_max(0.0, distance - halfwidth, rounding)};
auto const bend{rounding / slope};
// the bends of the slopes round off only away from the formation: under it the ground is the formation itself, a cut or a
// fill as shallow as the bend isn't let to sink or rise it
auto const bend{rounding / slope * std::clamp(beyond / std::max(rounding, 1e-6), 0.0, 1.0)};
auto const target{ground > formation ? -soft_max(-ground, -(formation + beyond / slope), bend) : soft_max(ground, formation - beyond / slope, bend)};
if (std::abs(target - ground) < 0.001)
return false;
Height = static_cast<float>(target);
return true;
return effect{target, distance - halfwidth, distance};
}
// manifest

View File

@@ -143,6 +143,15 @@ class modifier_shape
explicit modifier_shape(modifier const &Modifier);
// leads Height at specified point the way the modifier does; returns: true if the height changed
bool apply(double const X, double const Z, float &Height) const;
// what the modifier makes of Ground at the point, and how far the point lies off the formation: 0 or less on it; none out of
// its reach
struct effect
{
double height{0.0};
double outside{0.0};
double distance{0.0}; // from the axis
};
std::optional<effect> evaluate(double const X, double const Z, double const Ground) const;
bool reaches(glm::dvec2 const &Min, glm::dvec2 const &Max) const;
private:

View File

@@ -16,11 +16,15 @@ http://mozilla.org/MPL/2.0/.
#include "world/Traction.h"
#include "utilities/Globals.h"
#include "utilities/utilities.h"
#include "utilities/translation.h"
#include "editor/editorFormat.hpp"
#include <algorithm>
#include <array>
#include <fstream>
#include <functional>
#include <iterator>
#include <string_view>
namespace scene
{
@@ -207,7 +211,7 @@ bool node_layers::accepts(layer_handle Layer, std::string *Reason) const
{
if (Reason != nullptr)
{
*Reason = "the file is loaded after the scenario initialization (FirstInit), which only vehicles should follow";
*Reason = STR_C("the file is loaded after the scenario initialization (FirstInit), which only vehicles should follow");
}
return false;
}
@@ -355,16 +359,16 @@ bool node_layers::writable(layer_handle Layer, std::string *Reason) const
char const *reason{nullptr};
if (target.binary)
{
reason = "the file is loaded from binary terrain cache";
reason = STR_C("the file is loaded from binary terrain cache");
}
else if (target.sites.size() > 1)
{
// one definition yields several nodes, there's no telling which of them should be written back
reason = "the file is included more than once";
reason = STR_C("the file is included more than once");
}
else if (false == target.sites.empty() && target.sites.front().parameters)
{
reason = "the file is included with parameters";
reason = STR_C("the file is included with parameters");
}
if (reason != nullptr && Reason != nullptr)
{
@@ -396,7 +400,7 @@ bool node_layers::editable(basic_node const *Node, std::string *Reason) const
std::string reason;
if (Node->from_template())
{
reason = "it's a part of an include template (.inc)";
reason = STR_C("it's a part of an include template (.inc)");
}
else if (false == writable(nodelayer, &reason))
{
@@ -404,11 +408,11 @@ bool node_layers::editable(basic_node const *Node, std::string *Reason) const
}
else if (layer(nodelayer).locked)
{
reason = "its layer is locked";
reason = STR_C("its layer is locked");
}
else if (false == layer(nodelayer).visible || layer(nodelayer).removed)
{
reason = "its layer is hidden";
reason = STR_C("its layer is hidden");
}
if (false == reason.empty() && Reason != nullptr)
{
@@ -537,7 +541,9 @@ bool node_layers::is_descendant(layer_handle Layer, layer_handle const Ancestor)
std::string node_layers::path(layer_handle const Layer) const
{
return Global.asCurrentSceneryPath + layer(Layer).name;
auto const named{Global.asCurrentSceneryPath + layer(Layer).name};
auto const cased{FileCased(named)};
return cased.empty() ? named : cased;
}
// stores current state of the layer file, to detect later whether it was changed behind our back
@@ -557,12 +563,12 @@ layer_handle node_layers::create(std::string Name, layer_handle Parent, std::str
Parent = resolve(Parent);
if (false == valid(Parent) || layer(Parent).removed || layer(Parent).binary)
{
Reason = "the parent layer can't include other files";
Reason = STR_C("the parent layer can't include other files");
return null_handle;
}
if (layer(Parent).late)
{
Reason = "the parent layer is loaded after the scenario initialization (FirstInit), which only vehicles should follow";
Reason = STR_C("the parent layer is loaded after the scenario initialization (FirstInit), which only vehicles should follow");
return null_handle;
}
// file names in the scenery are processed in lower case, with forward slashes
@@ -572,12 +578,12 @@ layer_handle node_layers::create(std::string Name, layer_handle Parent, std::str
Name.erase(Name.find_last_not_of(" \t") + 1);
if (Name.empty())
{
Reason = "the layer needs a file name";
Reason = STR_C("the layer needs a file name");
return null_handle;
}
if (Name.find_first_of(" \t\";:*?<>|") != std::string::npos || Name.front() == '/' || Name.back() == '/' || contains(Name, ".."))
{
Reason = "the file name has to be a plain path inside the scenery directory, without spaces";
Reason = STR_C("the file name has to be a plain path inside the scenery directory, without spaces");
return null_handle;
}
if (std::filesystem::path(Name).extension().empty())
@@ -586,18 +592,18 @@ layer_handle node_layers::create(std::string Name, layer_handle Parent, std::str
}
if (Name.ends_with(".inc"))
{
Reason = ".inc files are include templates, not layers";
Reason = STR_C(".inc files are include templates, not layers");
return null_handle;
}
if (m_layers.size() >= static_cast<std::size_t>(std::numeric_limits<layer_handle>::max()))
{
Reason = "too many layers";
Reason = STR_C("too many layers");
return null_handle;
}
auto const taken{std::any_of(std::begin(m_layers), std::end(m_layers), [&](basic_layer const &Layer) { return Layer.name == Name && false == Layer.dead; })};
if (taken || FileExists(Global.asCurrentSceneryPath + Name))
{
Reason = "the file \"" + Name + "\" already exists";
Reason = format(STR_C("the file \"%s\" already exists"), Name.c_str());
return null_handle;
}
@@ -642,7 +648,7 @@ bool node_layers::can_remove(layer_handle Layer, std::string &Reason) const
{
if (false == listed(Layer) || layer(Layer).removed)
{
Reason = "no such layer";
Reason = STR_C("no such layer");
return false;
}
auto const &target{layer(Layer)};
@@ -652,22 +658,22 @@ bool node_layers::can_remove(layer_handle Layer, std::string &Reason) const
}
if (target.sites.empty())
{
Reason = "the scenario file can't be removed";
Reason = STR_C("the scenario file can't be removed");
return false;
}
if (target.binary)
{
Reason = "the file is loaded from binary terrain cache";
Reason = STR_C("the file is loaded from binary terrain cache");
return false;
}
if (target.sites.size() > 1)
{
Reason = "the file is included more than once";
Reason = STR_C("the file is included more than once");
return false;
}
if (target.sites.front().fixed)
{
Reason = "the file is included by an include template (.inc)";
Reason = STR_C("the file is included by an include template (.inc)");
return false;
}
return true;
@@ -733,12 +739,12 @@ bool node_layers::can_merge(layer_handle Source, layer_handle Target, std::strin
{
if (false == listed(Source) || false == listed(Target) || layer(Source).removed || layer(Target).removed)
{
Reason = "no such layer";
Reason = STR_C("no such layer");
return false;
}
if (Source == Target)
{
Reason = "pick another layer to merge into";
Reason = STR_C("pick another layer to merge into");
return false;
}
auto const &source{layer(Source)};
@@ -750,14 +756,14 @@ bool node_layers::can_merge(layer_handle Source, layer_handle Target, std::strin
if (is_descendant(Target, Source))
{
// the merged text would include the file it was put in
Reason = "the target layer is included by the merged one";
Reason = STR_C("the target layer is included by the merged one");
return false;
}
if (target.late != source.late && (source.created || source.sites.empty() || resolve(source.sites.front().parent) != Target))
{
// the content would change sides of the scenario initialization
Reason = (target.late ? "the target layer is loaded after the scenario initialization (FirstInit), which only vehicles should follow" :
"the merged layer is loaded after the scenario initialization (FirstInit), its content has to stay there");
Reason = (target.late ? STR_C("the target layer is loaded after the scenario initialization (FirstInit), which only vehicles should follow") :
STR_C("the merged layer is loaded after the scenario initialization (FirstInit), its content has to stay there"));
return false;
}
if (false == source.context_insert().matches(target.context_insert()))
@@ -768,7 +774,7 @@ bool node_layers::can_merge(layer_handle Source, layer_handle Target, std::strin
})};
if (unsaved)
{
Reason = "the merged layer has includes which weren't saved yet, save the scenery first";
Reason = STR_C("the merged layer has includes which weren't saved yet, save the scenery first");
return false;
}
}
@@ -779,7 +785,7 @@ bool node_layers::can_merge(layer_handle Source, layer_handle Target, std::strin
}
if (source.sites.empty())
{
Reason = "the scenario file can't be merged into another layer";
Reason = STR_C("the scenario file can't be merged into another layer");
return false;
}
if (false == writable(Source, &Reason))
@@ -788,7 +794,7 @@ bool node_layers::can_merge(layer_handle Source, layer_handle Target, std::strin
}
if (source.sites.front().fixed)
{
Reason = "the file is included by an include template (.inc)";
Reason = STR_C("the file is included by an include template (.inc)");
return false;
}
if (resolve(source.sites.front().parent) != Target)
@@ -797,7 +803,7 @@ bool node_layers::can_merge(layer_handle Source, layer_handle Target, std::strin
// and can't leave anything behind for what used to follow it
if (false == source.context_begin.matches(target.context_insert()) || false == source.context_end.matches(source.context_begin))
{
Reason = "the layers are loaded with different origin, rotation or scale";
Reason = STR_C("the layers are loaded with different origin, rotation or scale");
return false;
}
}
@@ -837,7 +843,25 @@ bool node_layers::pending() const
namespace
{
// marks of the lines the editor keeps track of
std::array<char const *, 3> const editormarks{"//$p", "//$b", "//$c"};
std::array<char const *, 6> const editormarks{"//$p", "//$h", "//$r", "//$b", "//$c", "//$o"};
// other tools put lines of their own after some of these marks: Rainsted its includes after //$r, the launcher the descriptions
// of the trainsets after //$o. The editor's lines of them are told by their first word
bool editor_line(char const *Mark, std::string const &Text)
{
static std::array<std::pair<std::string_view, std::array<std::string_view, 9>>, 3> const words{{
{"//$h", {"#", "alignment", "source", "ends", "norms", "pieces", "vertex", "element", "end"}},
{"//$r", {"#", "switch", "slip", "crossing", "double", "connection"}},
{"//$o", {"on"}},
}};
auto const lookup{std::find_if(words.begin(), words.end(), [&](auto const &Entry) { return Entry.first == Mark; })};
if (lookup == words.end())
{
return true;
}
auto const word{std::string_view{Text}.substr(0, Text.find_first_of(" \t"))};
return std::any_of(lookup->second.begin(), lookup->second.end(), [&](std::string_view const Known) { return false == Known.empty() && Known == word; });
}
} // namespace
void node_layers::scan(layer_handle const Layer)
@@ -874,6 +898,10 @@ void node_layers::scan(layer_handle const Layer)
text.pop_back();
}
text.erase(0, text.find_first_not_of(" \t"));
if (false == editor_line(mark, text))
{
break;
}
m_marked[Layer].push_back({mark, {static_cast<std::streamoff>(begin), static_cast<std::streamoff>(end)}, std::move(text)});
break;
}
@@ -953,14 +981,14 @@ bool node_layers::removable(instance_handle Instance, std::string *Reason) const
std::string reason;
if (false == tracked(Instance) || instance(Instance).dead)
{
reason = "it's a part of an include template (.inc)";
reason = STR_C("it's a part of an include template (.inc)");
}
else
{
auto const includelayer{resolve(instance(Instance).layer)};
if (false == valid(includelayer) || layer(includelayer).removed)
{
reason = "its layer is no longer a part of the scenery";
reason = STR_C("its layer is no longer a part of the scenery");
}
else if (false == writable(includelayer, &reason))
{
@@ -968,7 +996,7 @@ bool node_layers::removable(instance_handle Instance, std::string *Reason) const
}
else if (layer(includelayer).locked)
{
reason = "its layer is locked";
reason = STR_C("its layer is locked");
}
}
if (false == reason.empty() && Reason != nullptr)
@@ -1108,7 +1136,8 @@ void node_layers::rebuilt(instance_handle const Instance)
std::string scene::node_layers::check_sources() const
{
std::map<scene::layer_handle, std::string> files;
std::size_t good{0}, bad{0};
std::size_t good{0};
std::size_t bad{0};
std::string report;
for (auto const &[node, source] : m_sources)
{

View File

@@ -24,6 +24,8 @@ http://mozilla.org/MPL/2.0/.
#include "utilities/Globals.h"
#include "utilities/Logs.h"
#include "utilities/utilities.h"
#include "utilities/translation.h"
#include "editor/editorFormat.hpp"
#include <map>
#include <optional>
@@ -636,13 +638,13 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
save_result result;
if (m_layers.empty())
{
result.message = "The scenery wasn't opened for editing, there are no layers to save.";
result.message = STR_C("The scenery wasn't opened for editing, there are no layers to save");
return result;
}
auto const root{layer_handle{1}};
save_state state;
auto const fail = [&](std::string const &Message) {
result.message = "Scenery not saved: " + Message;
result.message = format(STR_C("Scenery not saved: %s"), Message.c_str());
ErrorLog(result.message);
return result;
};
@@ -965,10 +967,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
auto const path_text = [](TTrack &Path, glm::dvec3 const &Offset) {
auto const paths{Path.m_paths};
for (auto &path : Path.m_paths)
{
path.points[segment_data::point::start] -= Offset;
path.points[segment_data::point::end] -= Offset;
}
path.translate(-Offset);
std::string text;
Path.export_as_text(text);
Path.m_paths = paths;
@@ -1006,7 +1005,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
}
if (false == writable(source.layer))
{
unwritten.push_back(path->name().empty() ? std::string{"(noname)"} : path->name());
unwritten.push_back(path->name().empty() ? std::string{STR_C("(no name)")} : path->name());
continue;
}
if (false == load(source.layer))
@@ -1357,7 +1356,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
if (false == included.context.matches(layer(target).context_insert()))
{
// shouldn't happen, merge of layers is refused when it'd lead to this
return fail("include \"" + *included.file + "\" was placed for different origin, rotation or scale than its layer file receives it with");
return fail(format(STR_C("include \"%s\" was placed for different origin, rotation or scale than its layer file receives it with"), included.file->c_str()));
}
placed[target].emplace_back(static_cast<instance_handle>(idx + 1));
}
@@ -1634,7 +1633,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
std::filesystem::remove(path(leftover) + ".tmp", error);
}
std::filesystem::remove(path(output) + ".tmp", error);
return fail("can't write file \"" + layer(output).name + "\"");
return fail(format(STR_C("can't write file \"%s\""), layer(output).name.c_str()));
}
written.emplace_back(output);
}
@@ -1660,7 +1659,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
{
std::filesystem::remove(path(leftover) + ".tmp", error);
}
return fail("can't make a backup of file \"" + layer(output).name + "\": " + copyerror.message());
return fail(format(STR_C("can't make a backup of file \"%s\": %s"), layer(output).name.c_str(), copyerror.message().c_str()));
}
WriteLog("Scenery save: the triangles of the ground go from \"" + layer(output).name + "\", its previous content is kept in \"" + backup + "\"");
result.backups.emplace_back(backup);
@@ -1699,7 +1698,7 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
std::filesystem::remove(path(leftover) + ".tmp", error);
}
result.files.clear();
return fail("can't replace file \"" + layer(output).name + "\": " + cause);
return fail(format(STR_C("can't replace file \"%s\": %s"), layer(output).name.c_str(), cause.c_str()));
}
replaced.emplace_back(output);
result.files.emplace_back(layer(output).name);
@@ -1870,18 +1869,19 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
result.success = true;
if (result.files.empty())
{
result.message = "No changes to save.";
result.message = STR_C("No changes to save");
}
else
{
result.message = "Saved: ";
std::string files;
for (auto const &file : result.files)
{
result.message += file + (&file != &result.files.back() ? ", " : "");
files += file + (&file != &result.files.back() ? ", " : "");
}
result.message = format(STR_C("Saved: %s"), files.c_str());
for (auto const &backup : result.backups)
{
result.message += ". Backup before the ground was taken out: " + std::filesystem::path(backup).filename().string();
result.message += format(STR_C(". Backup before the ground was taken out: %s"), std::filesystem::path(backup).filename().string().c_str());
}
}
if (false == unwritten.empty())
@@ -1895,8 +1895,8 @@ save_result node_layers::save(std::vector<std::string> const &Rootstatements, st
{
names += ", ...";
}
auto const warning{std::to_string(unwritten.size()) + " changed nodes not saved, their files are included with parameters or more than once: " + names};
WriteLog("Scenery save: " + warning, logtype::generic);
WriteLog("Scenery save: " + std::to_string(unwritten.size()) + " changed nodes not saved, their files are included with parameters or more than once: " + names, logtype::generic);
auto const warning{format(STR_C("Changed nodes left unsaved (their files are included with parameters or more than once): %zu, %s"), unwritten.size(), names.c_str())};
result.message = result.files.empty() ? warning : result.message + ". " + warning;
}
return result;