mirror of
https://github.com/MaSzyna-EU07/maszyna.git
synced 2026-10-17 06:19:19 +02:00
* Text displays: accept characters above 127, in windows-1250 or utf-8 A text display (sub-model of type "text") looks up the sub-model of each character in a 256 entry table, and indexed it with a plain char. With char being signed, any character above 127 gave a negative index: reading the text went outside the table, and so did filling the table if a character sub-model had such a name. In effect the displays could only show ASCII, and a text with national characters could crash. The lookup is now keyed by unicode code point. Both the displayed text and the names of the character sub-models are decoded as utf-8 if they are valid utf-8, and as windows-1250 otherwise, so legacy (ansi) sceneries and models work next to utf-8 ones, and the two can be mixed. A short ansi text can be valid utf-8 by accident (two upper case accented letters in a row will do); when both readings are possible the one which finds more of its characters in the model is used. The rest is unchanged: the first character of the text still isn't displayed, the characters are still drawn in the translucent pass only. A missing text pointer no longer gets dereferenced. * Text displays: prepare the characters once for each text The sequence of character sub-models was cached for the last text only, so displays of several instances drawn in turn decoded their texts again on every draw. It is now kept for each text the display shows; the texts of the instances don't change, so a draw costs a single lookup. Also the comments added in the renderers are now in English. * Text displays: move the text decoding to utilities utf8_to_utf32() and win1250_to_utf32(), with the windows-1250 table, now sit next to win1250_to_utf8(). The existing function maps only the Polish letters and passes other bytes above 127 through, so it can't serve the displays. * Text displays: address the SonarCloud findings std::array instead of the C-style arrays, the utf-8 sequence length worked out in a function of its own instead of nested conditional operators, std::byte for the bit operations, std::ranges::count_if and contains(). The character loop of the renderers moved to Render_Alpha_text(), which takes the nesting back to the limit, and the static pasText is accessed through the class.
672 lines
19 KiB
C++
672 lines
19 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/.
|
|
*/
|
|
/*
|
|
MaSzyna EU07 - SPKS
|
|
Brakes.
|
|
Copyright (C) 2007-2014 Maciej Cierniak
|
|
*/
|
|
//#include "stdafx.h"
|
|
//
|
|
//#include <sys/types.h>
|
|
//#include <sys/stat.h>
|
|
#include <charconv>
|
|
#include <ranges>
|
|
//#ifndef WIN32
|
|
//#include <unistd.h>
|
|
//#endif
|
|
//
|
|
//#ifdef WIN32
|
|
//#define stat _stat
|
|
//#endif
|
|
|
|
#include "utilities/utilities.h"
|
|
#include "utilities/Globals.h"
|
|
#include "utilities/parser.h"
|
|
#include "utilities/U8.h"
|
|
#include "utilities/Logs.h"
|
|
|
|
|
|
|
|
// TODO: This shouldn't be in Globals?
|
|
bool DebugModeFlag = false;
|
|
bool FreeFlyModeFlag = false;
|
|
bool EditorModeFlag = false;
|
|
bool DebugCameraFlag = false;
|
|
bool DebugTractionFlag = false;
|
|
|
|
std::string Now()
|
|
{
|
|
#if defined(__APPLE__)
|
|
auto t = std::chrono::system_clock::to_time_t(std::chrono::system_clock::now());
|
|
std::tm tm{};
|
|
localtime_r(&t, &tm);
|
|
char buf[64];
|
|
std::strftime(buf, sizeof(buf), "%c", &tm);
|
|
return std::string(buf);
|
|
#else
|
|
auto now = std::chrono::system_clock::now();
|
|
auto local = std::chrono::current_zone()->to_local(now);
|
|
return std::format("{:%c}", local);
|
|
#endif
|
|
}
|
|
|
|
std::filesystem::path user_config_path(const std::string &filename)
|
|
{
|
|
namespace fs = std::filesystem;
|
|
#if defined(_WIN32)
|
|
if (const char *appdata = std::getenv("APPDATA"))
|
|
return fs::path(appdata) / "MaSzyna" / filename;
|
|
#elif defined(__APPLE__)
|
|
if (const char *home = std::getenv("HOME"))
|
|
return fs::path(home) / "Library" / "Application Support" / "MaSzyna" / filename;
|
|
#else
|
|
if (const char *home = std::getenv("HOME"))
|
|
return fs::path(home) / ".config" / "MaSzyna" / filename;
|
|
#endif
|
|
return {}; // env var missing → caller falls back to CWD-relative filename
|
|
}
|
|
|
|
// zwraca różnicę czasu
|
|
// jeśli pierwsza jest aktualna, a druga rozkładowa, to ujemna oznacza opóżnienie
|
|
// na dłuższą metę trzeba uwzględnić datę, jakby opóżnienia miały przekraczać 12h (towarowych)
|
|
double CompareTime(double t1h, double t1m, double t2h, double t2m)
|
|
{
|
|
|
|
if (t2h < 0)
|
|
return 0;
|
|
else
|
|
{
|
|
auto t = (t2h - t1h) * 60 + t2m - t1m; // jeśli t2=00:05, a t1=23:50, to różnica wyjdzie ujemna
|
|
if (t < -720) // jeśli różnica przekracza 12h na minus
|
|
t = t + 1440; // to dodanie doby minut;else
|
|
if (t > 720) // jeśli przekracza 12h na plus
|
|
t = t - 1440; // to odjęcie doby minut
|
|
return t;
|
|
}
|
|
}
|
|
|
|
bool SetFlag(int &Flag, int const Value)
|
|
{
|
|
|
|
if (Value > 0)
|
|
{
|
|
if (false == TestFlag(Flag, Value))
|
|
{
|
|
Flag |= Value;
|
|
return true; // true, gdy było wcześniej 0 i zostało ustawione
|
|
}
|
|
}
|
|
else if (Value < 0)
|
|
{
|
|
// Value jest ujemne, czyli zerowanie flagi
|
|
return ClearFlag(Flag, -Value);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ClearFlag(int &Flag, int const Value)
|
|
{
|
|
|
|
if (true == TestFlag(Flag, Value))
|
|
{
|
|
Flag &= ~Value;
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
double Random(double min, double max)
|
|
{
|
|
if (max < min) { std::swap(min, max); } // std::uniform_real_distribution requires min <= max (inverted bounds are UB)
|
|
std::uniform_real_distribution<double> dist(min, max);
|
|
return dist(Global.random_engine);
|
|
}
|
|
|
|
int Random(int min, int max)
|
|
{
|
|
if (max < min) { std::swap(min, max); } // std::uniform_int_distribution requires min <= max (inverted bounds are UB)
|
|
std::uniform_int_distribution<int> dist(min, max);
|
|
return dist(Global.random_engine);
|
|
}
|
|
|
|
std::uint64_t true_random_seed()
|
|
{
|
|
try
|
|
{
|
|
std::random_device rd;
|
|
if (rd.entropy() > 0.0)
|
|
{
|
|
// NOTE: both halves have to be 64 bit wide before the shift - shifting a std::uint32_t
|
|
// by 32 is undefined behaviour, and drops the high half of the seed
|
|
std::uint64_t const high{rd()};
|
|
std::uint64_t const low{rd()};
|
|
return (high << 32) | low;
|
|
}
|
|
}
|
|
catch (std::exception const &Error)
|
|
{
|
|
// std::random_device throws when the machine has no usable hardware entropy source;
|
|
// the clock below stands in for it, which is worth saying out loud since it is a
|
|
// weaker seed than the caller asked for
|
|
WriteLog(std::format("random: no hardware entropy source ({}), seeding from the clock instead", Error.what()));
|
|
}
|
|
return static_cast<std::uint64_t>(std::chrono::high_resolution_clock::now().time_since_epoch().count());
|
|
}
|
|
|
|
std::uint32_t seed_of(std::string const &Text)
|
|
{
|
|
if (std::uint32_t number{}; std::from_chars(Text.data(), Text.data() + Text.size(), number).ec == std::errc{})
|
|
{
|
|
return number;
|
|
}
|
|
std::uint32_t hash{};
|
|
for (auto const character : Text)
|
|
{
|
|
hash = hash * 31u + static_cast<std::uint32_t>(static_cast<unsigned char>(character));
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
std::string generate_uuid_v4()
|
|
{
|
|
std::uniform_int_distribution<int> dist(0, 255);
|
|
|
|
std::array<uint8_t, 16> bytes;
|
|
for (auto &b : bytes)
|
|
b = static_cast<uint8_t>(dist(Global.random_engine));
|
|
|
|
// UUID v4 (RFC 4122)
|
|
bytes[6] = bytes[6] & 0x0F | 0x40;
|
|
bytes[8] = bytes[8] & 0x3F | 0x80;
|
|
|
|
char buf[37]; // 36 znaków + \0
|
|
std::snprintf(buf, sizeof(buf),
|
|
"%02x%02x%02x%02x-"
|
|
"%02x%02x-"
|
|
"%02x%02x-"
|
|
"%02x%02x-"
|
|
"%02x%02x%02x%02x%02x%02x",
|
|
bytes[0], bytes[1], bytes[2], bytes[3], bytes[4], bytes[5], bytes[6], bytes[7], bytes[8], bytes[9], bytes[10], bytes[11], bytes[12], bytes[13], bytes[14], bytes[15]);
|
|
|
|
return std::string(buf);
|
|
}
|
|
|
|
double LocalRandom(double a, double b)
|
|
{
|
|
if (b < a) { std::swap(a, b); } // std::uniform_real_distribution requires a <= b (inverted bounds are UB)
|
|
std::uniform_real_distribution<double> dist(a, b);
|
|
return dist(Global.local_random_engine);
|
|
}
|
|
|
|
bool FuzzyLogic(double Test, double Threshold, double Probability)
|
|
{
|
|
if (Test > Threshold && !DebugModeFlag)
|
|
return Random() < Probability * Threshold * 1.0 / Test /*im wiekszy Test tym wieksza szansa*/;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
bool FuzzyLogicAI(double Test, double Threshold, double Probability)
|
|
{
|
|
if (Test > Threshold)
|
|
return Random() < Probability * Threshold * 1.0 / Test /*im wiekszy Test tym wieksza szansa*/;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
std::vector<std::string> Split(std::string_view s, char delim)
|
|
{ // dzieli tekst na wektor tekstow
|
|
std::vector<std::string> out;
|
|
for (const auto& part : s | std::ranges::views::split(delim))
|
|
out.emplace_back(part.begin(), part.end());
|
|
return out;
|
|
}
|
|
|
|
std::pair<std::string, int> split_string_and_number(std::string const &Key)
|
|
{
|
|
|
|
auto const indexstart{Key.find_first_of("-1234567890")};
|
|
auto const indexend{Key.find_first_not_of("-1234567890", indexstart)};
|
|
if (indexstart != std::string::npos)
|
|
{
|
|
return {Key.substr(0, indexstart), std::stoi(Key.substr(indexstart, indexend - indexstart))};
|
|
}
|
|
return {Key, 0};
|
|
}
|
|
|
|
std::string to_string(int Value, int width)
|
|
{
|
|
std::ostringstream o;
|
|
o << std::setw(width) << Value;
|
|
return std::move(o).str();
|
|
};
|
|
|
|
std::string to_string(double Value, int precision)
|
|
{
|
|
std::ostringstream o;
|
|
o << std::fixed << std::setprecision(precision) << Value;
|
|
return std::move(o).str();
|
|
};
|
|
|
|
std::string to_string(double const Value, int const Precision, int const Width)
|
|
{
|
|
std::ostringstream o;
|
|
o << std::setw(Width) << std::fixed << std::setprecision(Precision) << Value;
|
|
return std::move(o).str();
|
|
};
|
|
|
|
std::string to_hex_str(int const Value, int const Width)
|
|
{
|
|
std::ostringstream o;
|
|
o << "0x" << std::uppercase << std::setfill('0') << std::setw(Width) << std::hex << Value;
|
|
return o.str();
|
|
};
|
|
|
|
std::string const fractionlabels[] = {U8(" "), U8("¹"), U8("²"), U8("³"), U8("⁴"), U8("⁵"), U8("⁶"), U8("⁷"), U8("⁸"), U8("⁹")};
|
|
|
|
std::string to_minutes_str(float const Minutes, bool const Leadingzero, int const Width)
|
|
{
|
|
|
|
float minutesintegral;
|
|
auto const minutesfractional{std::modf(Minutes, &minutesintegral)};
|
|
auto const width{Width - 1};
|
|
auto minutes = std::string(width - 1, ' ') + (Leadingzero ? std::to_string(100 + minutesintegral).substr(1, 2) : to_string(minutesintegral, 0));
|
|
return minutes.substr(minutes.size() - width, width) + fractionlabels[static_cast<int>(std::floor(minutesfractional * 10 + 0.1))];
|
|
}
|
|
|
|
int stol_def(const std::string &str, const int &DefaultValue)
|
|
{
|
|
|
|
int result{DefaultValue};
|
|
std::stringstream converter;
|
|
converter << str;
|
|
converter >> result;
|
|
return result;
|
|
}
|
|
|
|
std::string ToLower(std::string const &text)
|
|
{
|
|
|
|
auto lowercase{text};
|
|
std::transform(std::begin(text), std::end(text), std::begin(lowercase), [](unsigned char c) { return std::tolower(c); });
|
|
return lowercase;
|
|
}
|
|
|
|
std::string ToUpper(std::string const &text)
|
|
{
|
|
|
|
auto uppercase{text};
|
|
std::transform(std::begin(text), std::end(text), std::begin(uppercase), [](unsigned char c) { return std::toupper(c); });
|
|
return uppercase;
|
|
}
|
|
|
|
// replaces polish letters with basic ascii
|
|
void win1250_to_ascii(std::string &Input)
|
|
{
|
|
|
|
std::unordered_map<char, char> const charmap{{165, 'A'}, {198, 'C'}, {202, 'E'}, {163, 'L'}, {209, 'N'}, {211, 'O'}, {140, 'S'}, {143, 'Z'}, {175, 'Z'},
|
|
{185, 'a'}, {230, 'c'}, {234, 'e'}, {179, 'l'}, {241, 'n'}, {243, 'o'}, {156, 's'}, {159, 'z'}, {191, 'z'}};
|
|
std::unordered_map<char, char>::const_iterator lookup;
|
|
for (auto &input : Input)
|
|
{
|
|
if ((lookup = charmap.find(input)) != charmap.end())
|
|
input = lookup->second;
|
|
}
|
|
}
|
|
|
|
std::string win1250_to_utf8(const std::string &Input)
|
|
{
|
|
std::unordered_map<char, std::string> const charmap{{165, "Ą"}, {198, "Ć"}, {202, "Ę"}, {163, "Ł"}, {209, "Ń"}, {211, "Ó"}, {140, "Ś"}, {143, "Ź"}, {175, "Ż"},
|
|
{185, "ą"}, {230, "ć"}, {234, "ę"}, {179, "ł"}, {241, "ń"}, {243, "ó"}, {156, "ś"}, {159, "ź"}, {191, "ż"}};
|
|
std::string output;
|
|
std::unordered_map<char, std::string>::const_iterator lookup;
|
|
for (auto &input : Input)
|
|
{
|
|
if ((lookup = charmap.find(input)) != charmap.end())
|
|
output += lookup->second;
|
|
else
|
|
output += input;
|
|
}
|
|
return output;
|
|
}
|
|
|
|
namespace
|
|
{
|
|
// unicode code points of the upper half of the windows-1250 code page; 0 marks the unassigned bytes
|
|
std::array<char32_t, 128> const win1250_upperhalf{
|
|
0x20AC, 0x0000, 0x201A, 0x0000, 0x201E, 0x2026, 0x2020, 0x2021, 0x0000, 0x2030, 0x0160, 0x2039, 0x015A, 0x0164, 0x017D, 0x0179,
|
|
0x0000, 0x2018, 0x2019, 0x201C, 0x201D, 0x2022, 0x2013, 0x2014, 0x0000, 0x2122, 0x0161, 0x203A, 0x015B, 0x0165, 0x017E, 0x017A,
|
|
0x00A0, 0x02C7, 0x02D8, 0x0141, 0x00A4, 0x0104, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x015E, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x017B,
|
|
0x00B0, 0x00B1, 0x02DB, 0x0142, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x0105, 0x015F, 0x00BB, 0x013D, 0x02DD, 0x013E, 0x017C,
|
|
0x0154, 0x00C1, 0x00C2, 0x0102, 0x00C4, 0x0139, 0x0106, 0x00C7, 0x010C, 0x00C9, 0x0118, 0x00CB, 0x011A, 0x00CD, 0x00CE, 0x010E,
|
|
0x0110, 0x0143, 0x0147, 0x00D3, 0x00D4, 0x0150, 0x00D6, 0x00D7, 0x0158, 0x016E, 0x00DA, 0x0170, 0x00DC, 0x00DD, 0x0162, 0x00DF,
|
|
0x0155, 0x00E1, 0x00E2, 0x0103, 0x00E4, 0x013A, 0x0107, 0x00E7, 0x010D, 0x00E9, 0x0119, 0x00EB, 0x011B, 0x00ED, 0x00EE, 0x010F,
|
|
0x0111, 0x0144, 0x0148, 0x00F3, 0x00F4, 0x0151, 0x00F6, 0x00F7, 0x0159, 0x016F, 0x00FA, 0x0171, 0x00FC, 0x00FD, 0x0163, 0x02D9};
|
|
|
|
// returns number of bytes of the utf-8 sequence started by provided byte, or 0 if it can't start one
|
|
std::size_t utf8_sequence_length(std::byte const Lead)
|
|
{
|
|
if ((Lead & std::byte{0x80}) == std::byte{0x00})
|
|
{
|
|
return 1;
|
|
}
|
|
if ((Lead & std::byte{0xE0}) == std::byte{0xC0})
|
|
{
|
|
return 2;
|
|
}
|
|
if ((Lead & std::byte{0xF0}) == std::byte{0xE0})
|
|
{
|
|
return 3;
|
|
}
|
|
if ((Lead & std::byte{0xF8}) == std::byte{0xF0})
|
|
{
|
|
return 4;
|
|
}
|
|
return 0; // a continuation byte, or a lead of a sequence utf-8 doesn't allow
|
|
}
|
|
} // namespace
|
|
|
|
bool utf8_to_utf32(std::string const &Text, std::u32string &Output)
|
|
{
|
|
// lowest code point which needs given number of bytes; shorter values are overlong forms
|
|
static std::array<char32_t, 5> const minimum{0, 0, 0x80, 0x800, 0x10000};
|
|
|
|
Output.clear();
|
|
auto const size{Text.size()};
|
|
for (std::size_t idx = 0; idx < size;)
|
|
{
|
|
auto const lead{static_cast<std::byte>(Text[idx])};
|
|
auto const length{utf8_sequence_length(lead)};
|
|
if (length == 0 || idx + length > size)
|
|
{
|
|
return false;
|
|
}
|
|
auto codepoint{std::to_integer<char32_t>(lead & (std::byte{0xFF} >> (length > 1 ? length + 1 : 1)))};
|
|
for (std::size_t offset = 1; offset < length; ++offset)
|
|
{
|
|
auto const next{static_cast<std::byte>(Text[idx + offset])};
|
|
if ((next & std::byte{0xC0}) != std::byte{0x80})
|
|
{
|
|
return false;
|
|
}
|
|
codepoint = (codepoint << 6) | std::to_integer<char32_t>(next & std::byte{0x3F});
|
|
}
|
|
if (codepoint < minimum[length] || codepoint > 0x10FFFF || (codepoint >= 0xD800 && codepoint <= 0xDFFF))
|
|
{
|
|
return false;
|
|
}
|
|
Output += codepoint;
|
|
idx += length;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void win1250_to_utf32(std::string const &Text, std::u32string &Output)
|
|
{
|
|
Output.clear();
|
|
for (auto const character : Text)
|
|
{
|
|
auto const value{std::to_integer<std::size_t>(static_cast<std::byte>(character))};
|
|
Output += (value < 0x80 ? static_cast<char32_t>(value) : win1250_upperhalf[value - 0x80]);
|
|
}
|
|
}
|
|
|
|
// Ra: tymczasowe rozwiązanie kwestii zagranicznych (czeskich) napisów
|
|
char charsetconversiontable[] = "E?,?\"_++?%S<STZZ?`'\"\".--??s>stzz"
|
|
" ^^L$A|S^CS<--RZo±,l'uP.,as>L\"lz"
|
|
"RAAAALCCCEEEEIIDDNNOOOOxRUUUUYTB"
|
|
"raaaalccceeeeiiddnnoooo-ruuuuyt?";
|
|
|
|
// wycięcie liter z ogonkami
|
|
std::string Bezogonkow(std::string Input, bool const Underscorestospaces)
|
|
{
|
|
|
|
char const extendedcharsetbit{static_cast<char>(0x80)};
|
|
char const space{' '};
|
|
char const underscore{'_'};
|
|
|
|
for (auto &input : Input)
|
|
{
|
|
if (input & extendedcharsetbit)
|
|
{
|
|
input = charsetconversiontable[input ^ extendedcharsetbit];
|
|
}
|
|
else if (input < space)
|
|
{
|
|
input = space;
|
|
}
|
|
else if (Underscorestospaces && input == underscore)
|
|
{
|
|
input = space;
|
|
}
|
|
}
|
|
|
|
return Input;
|
|
}
|
|
|
|
template <> bool extract_value(bool &Variable, std::string const &Key, std::string const &Input, std::string const &Default)
|
|
{
|
|
|
|
auto value = extract_value(Key, Input);
|
|
if (false == value.empty())
|
|
{
|
|
// set the specified variable to retrieved value
|
|
Variable = ToLower(value) == "yes";
|
|
return true; // located the variable
|
|
}
|
|
else
|
|
{
|
|
// set the variable to provided default value
|
|
if (false == Default.empty())
|
|
{
|
|
Variable = ToLower(Default) == "yes";
|
|
}
|
|
return false; // couldn't locate the variable in provided input
|
|
}
|
|
}
|
|
|
|
bool FileExists(std::string const &Filename)
|
|
{
|
|
return std::filesystem::exists(Filename);
|
|
}
|
|
|
|
std::pair<std::string, std::string> FileExists(std::vector<std::string> const &Names, std::vector<std::string> const &Extensions)
|
|
{
|
|
|
|
for (auto const &name : Names)
|
|
{
|
|
for (auto const &extension : Extensions)
|
|
{
|
|
if (FileExists(name + extension))
|
|
{
|
|
return {name, extension};
|
|
}
|
|
}
|
|
}
|
|
// nothing found
|
|
return {{}, {}};
|
|
}
|
|
|
|
// returns time of last modification for specified file
|
|
std::time_t last_modified(std::string const &Filename)
|
|
{
|
|
std::string fn = Filename;
|
|
struct stat filestat;
|
|
if (::stat(fn.c_str(), &filestat) == 0)
|
|
return filestat.st_mtime;
|
|
else
|
|
return 0;
|
|
}
|
|
|
|
std::tm local_tm(std::time_t Time)
|
|
{
|
|
std::tm tm{};
|
|
#ifdef _WIN32
|
|
localtime_s(&tm, &Time);
|
|
#else
|
|
localtime_r(&Time, &tm);
|
|
#endif
|
|
return tm;
|
|
}
|
|
|
|
std::tm utc_tm(std::time_t Time)
|
|
{
|
|
std::tm tm{};
|
|
#ifdef _WIN32
|
|
gmtime_s(&tm, &Time);
|
|
#else
|
|
gmtime_r(&Time, &tm);
|
|
#endif
|
|
return tm;
|
|
}
|
|
|
|
// potentially erases file extension from provided file name. returns: true if extension was removed, false otherwise
|
|
bool erase_extension(std::string &Filename)
|
|
{
|
|
|
|
auto const extensionpos{Filename.rfind('.')};
|
|
|
|
if (extensionpos == std::string::npos)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (extensionpos != Filename.rfind("..") + 1)
|
|
{
|
|
// we can get extension for .mat or, in legacy files, some image format. just trim it and set it to material file extension
|
|
Filename.erase(extensionpos);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void erase_leading_slashes(std::string &Filename)
|
|
{
|
|
auto pos = Filename.find_first_not_of('/');
|
|
Filename.erase(0, pos);
|
|
}
|
|
|
|
// potentially replaces backward slashes in provided file path with unix-compatible forward slashes
|
|
void replace_slashes(std::string &Filename)
|
|
{
|
|
std::ranges::replace(Filename, '\\', '/');
|
|
}
|
|
|
|
// returns potential path part from provided file name
|
|
std::string substr_path(std::string const &Filename)
|
|
{
|
|
// String::substr returns new string so substr_path has to return std::string
|
|
if (auto pos = Filename.rfind('/'); pos != std::string::npos)
|
|
return Filename.substr(0, pos + 1);
|
|
return {};
|
|
}
|
|
|
|
// returns length of common prefix between two provided strings
|
|
std::ptrdiff_t len_common_prefix(std::string_view a, std::string_view b)
|
|
{
|
|
auto [it1, it2] = std::ranges::mismatch(a, b);
|
|
return std::distance(a.begin(), it1);
|
|
}
|
|
|
|
// returns true if provided string contains another provided string
|
|
bool contains(std::string_view const String, std::string_view Substring)
|
|
{
|
|
// To be replaced with string::contains in C++ 23
|
|
return String.find(Substring) != std::string::npos;
|
|
}
|
|
|
|
bool contains(std::string_view const String, char Character)
|
|
{
|
|
// To be replaced with string::contains in C++ 23
|
|
return String.find(Character) != std::string::npos;
|
|
}
|
|
|
|
// helper, restores content of a 3d vector from provided input stream
|
|
// TODO: review and clean up the helper routines, there's likely some redundant ones
|
|
|
|
glm::dvec3 LoadPoint(cParser &Input)
|
|
{
|
|
// pobranie współrzędnych punktu
|
|
glm::dvec3 point;
|
|
Input.getTokens(3);
|
|
Input >> point.x >> point.y >> point.z;
|
|
return point;
|
|
}
|
|
|
|
// extracts a group of tokens from provided data stream, returns one of them picked randomly
|
|
std::string deserialize_random_set(cParser &Input, char const *Break)
|
|
{
|
|
|
|
auto token{Input.getToken<std::string>(true, Break)};
|
|
std::replace(token.begin(), token.end(), '\\', '/');
|
|
if (token != "[")
|
|
{
|
|
// simple case, single token
|
|
return token;
|
|
}
|
|
// if instead of a single token we've encountered '[' this marks a beginning of a random set
|
|
// we retrieve all entries, then return a random one
|
|
std::vector<std::string> tokens;
|
|
while ((token = deserialize_random_set(Input, Break)) != "" && token != "]")
|
|
{
|
|
tokens.emplace_back(token);
|
|
}
|
|
if (false == tokens.empty())
|
|
{
|
|
std::shuffle(std::begin(tokens), std::end(tokens), Global.random_engine);
|
|
return tokens.front();
|
|
}
|
|
else
|
|
{
|
|
// shouldn't ever get here but, eh
|
|
return "";
|
|
}
|
|
}
|
|
|
|
// returns hash of provided string, combined with provided seed
|
|
std::uint32_t hash_string(std::string const &String, std::uint32_t const Seed)
|
|
{
|
|
// FNV-1a, 32 bit, with the seed mixed into the standard offset basis
|
|
std::uint32_t hash{2166136261u ^ Seed};
|
|
for (auto const character : String)
|
|
{
|
|
hash ^= static_cast<std::uint32_t>(static_cast<unsigned char>(character));
|
|
hash *= 16777619u;
|
|
}
|
|
return hash;
|
|
}
|
|
|
|
// extracts a group of tokens from provided data stream, returns all of them
|
|
std::vector<std::string> deserialize_set(cParser &Input, char const *Break)
|
|
{
|
|
|
|
std::vector<std::string> tokens;
|
|
|
|
auto token{Input.getToken<std::string>(true, Break)};
|
|
std::ranges::replace(token, '\\', '/');
|
|
if (token != "[")
|
|
{
|
|
// simple case, single token
|
|
if (false == token.empty())
|
|
{
|
|
tokens.emplace_back(token);
|
|
}
|
|
return tokens;
|
|
}
|
|
// '[' marks a beginning of a set, retrieve all entries until it's closed
|
|
while ((token = deserialize_random_set(Input, Break)) != "" && token != "]")
|
|
{
|
|
tokens.emplace_back(token);
|
|
}
|
|
return tokens;
|
|
}
|