diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6e4a74ba..c9ce73bd 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -23,13 +23,13 @@ jobs: env: BUILD_WRAPPER_OUT_DIR: bw-output steps: - - uses: actions/checkout@v5 + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 with: fetch-depth: 0 submodules: recursive - name: Set up Python 3.14 - uses: actions/setup-python@v6 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: '3.14' @@ -106,7 +106,7 @@ jobs: -C "$GITHUB_WORKSPACE" . - name: Upload analysis input - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: sonar-input path: /tmp/workspace.tar.gz diff --git a/.github/workflows/build_bin.yml b/.github/workflows/build_bin.yml index 255c89a6..2f1781ed 100644 --- a/.github/workflows/build_bin.yml +++ b/.github/workflows/build_bin.yml @@ -33,14 +33,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: fetch-depth: 0 submodules: recursive - name: Setup Python 3.14 id: python - uses: actions/setup-python@v6 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: "3.14" architecture: "x64" @@ -115,7 +115,7 @@ jobs: -Force - name: Upload Windows build - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: maszyna-windows-x64-${{ env.CONFIG_WIN }} path: | @@ -124,7 +124,7 @@ jobs: retention-days: 14 - name: Upload Windows debug symbols - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: maszyna-windows-x64-symbols-${{ env.CONFIG_WIN }} path: | @@ -144,14 +144,14 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0 with: fetch-depth: 0 submodules: recursive - name: Setup Python 3.14 id: python - uses: actions/setup-python@v6 + uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0 with: python-version: "3.14" @@ -256,7 +256,7 @@ jobs: cp -R lang/. build/bin/lang/ - name: Upload Linux build - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 with: name: maszyna-linux-x64-${{ env.CONFIG_LINUX }} path: | diff --git a/.github/workflows/sonar.yml b/.github/workflows/sonar.yml index 66213c23..b2951189 100644 --- a/.github/workflows/sonar.yml +++ b/.github/workflows/sonar.yml @@ -64,15 +64,13 @@ jobs: if: github.event.action == 'completed' && github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-24.04 permissions: - contents: read # checkout trusted config + contents: read # fetch trusted config actions: read # download artifact from the Build run statuses: write # post the commit status pull-requests: read # verify PR head sha via the API env: BUILD_WRAPPER_OUT_DIR: bw-output - # .trusted/ holds the checkout of master used to restore the scanner config; - # it is not part of the analysed change. - SONAR_EXCLUSIONS: betterRenderer/**,ref/**,imgui/**,stb/**,build/**,bw-output/**,.trusted/** + SONAR_EXCLUSIONS: betterRenderer/**,ref/**,imgui/**,stb/**,build/**,bw-output/** SONAR_SCANNER_JAVA_OPTS: -Xmx5g steps: - name: Post pending status @@ -85,16 +83,23 @@ jobs: -f description="Analysis in progress" \ -f target_url="$RUN_URL" - # Trusted copy of the repo config, used to overwrite anything the PR - # might have tampered with (sonar-project.properties etc). - - name: Checkout trusted config from master - uses: actions/checkout@v5 - with: - ref: master - path: .trusted + # Trusted copy of the scanner config, used to overwrite anything the PR + # might have tampered with. Fetched as a single file through the API rather + # than with actions/checkout: nothing gets checked out in this privileged + # context. Kept outside the workspace, so the archive can't overwrite it. + - name: Fetch trusted config from master + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + set -euo pipefail + url="repos/${{ github.repository }}/contents/sonar-project.properties?ref=master" + if ! gh api -H "Accept: application/vnd.github.raw" "$url" > /tmp/trusted-sonar-project.properties; then + echo "::warning::no sonar-project.properties on master" + rm -f /tmp/trusted-sonar-project.properties + fi - name: Download analysis input - uses: actions/download-artifact@v4 + uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0 with: name: sonar-input path: /tmp/sonar-input @@ -104,13 +109,7 @@ jobs: - name: Unpack analysis input run: | set -euo pipefail - # The archive holds the whole PR workspace and is unpacked over this one, - # .trusted/ included. Move the trusted config out of reach first, or a PR - # carrying its own .trusted/sonar-project.properties would have it planted - # here and restored below as if it came from master. - if [ -f "$GITHUB_WORKSPACE/.trusted/sonar-project.properties" ]; then - cp "$GITHUB_WORKSPACE/.trusted/sonar-project.properties" /tmp/trusted-sonar-project.properties - fi + # The archive holds the whole PR workspace and is unpacked over this one. # Nothing may be written outside the workspace: reject absolute members and # any member reaching through a parent directory before unpacking anything. tar -tzf /tmp/sonar-input/workspace.tar.gz > /tmp/archive-members.txt diff --git a/McZapkie/MOVER.h b/McZapkie/MOVER.h index e7bd1d23..accbbbaa 100644 --- a/McZapkie/MOVER.h +++ b/McZapkie/MOVER.h @@ -1732,7 +1732,11 @@ class TMoverParameters // bounds-safe read access to MotorParam TMotorParameters const &motor_param(int const Position) const { - return MotorParam[std::clamp(Position, 0, MotorParametersArraySize)]; + if (Position < 0) + return MotorParam[0]; + if (Position > MotorParametersArraySize) + return MotorParam[MotorParametersArraySize]; + return MotorParam[Position]; } TTransmision Transmision; // record {liczba zebow przekladni} diff --git a/McZapkie/Mover.cpp b/McZapkie/Mover.cpp index 82dd245f..ff60c27c 100644 --- a/McZapkie/Mover.cpp +++ b/McZapkie/Mover.cpp @@ -1752,6 +1752,11 @@ void TMoverParameters::LowVoltagePowerCheck(double const Deltatime) void TMoverParameters::PowerCouplersCheck(double const Deltatime, coupling const Coupling) { + if (Coupling != coupling::highvoltage && Coupling != coupling::power110v && Coupling != coupling::power24v) + { + // only power couplings carry voltage + return; + } auto localvoltage{0.0}; @@ -2403,16 +2408,14 @@ bool TMoverParameters::IncMainCtrl(int CtrlSpeed) if (CtrlSpeed > 1) { OK = IncMainCtrl(1) && IncMainCtrl(CtrlSpeed - 1); // a fail will propagate up the recursion chain. should this be || instead? + break; } - else + ++MainCtrlPos; + OK = true; + if (EIMCtrlType == 0 && ( SpeedCtrlAutoTurnOffFlag & 1 ) == 1 && MainCtrlActualPos != MainCtrlPos) { - ++MainCtrlPos; - OK = true; - if (EIMCtrlType == 0 && ( SpeedCtrlAutoTurnOffFlag & 1 ) == 1 && MainCtrlActualPos != MainCtrlPos) - { - DecScndCtrl(2); - SpeedCtrlUnit.IsActive = false; - } + DecScndCtrl(2); + SpeedCtrlUnit.IsActive = false; } break; } @@ -6392,10 +6395,7 @@ double TMoverParameters::TractionForce(double dt) // PosRatio = 1.0 * (PosRatio * 0 + 1) * PosRatio; // 1 * 1 * PosRatio = PosRatio Hamulec->SetED(0); // (Hamulec as TLSt).SetLBP(LocBrakePress); - if (PosRatio > eimv_pr) - tmp = 4; - else - tmp = 4; // szybkie malenie, powolne wzrastanie + tmp = 4; // szybkie malenie, powolne wzrastanie } dmoment = eimv[eimv_Fful]; // NOTE: the commands to operate the sandbox are likely to conflict with other similar ai decisions @@ -8022,8 +8022,6 @@ double TMoverParameters::dizel_fillcheck(int mcp, double dt) } if (enrot > nreg) // nad predkoscia regulatora zeruj dawke realfill = 0; - if (enrot < nreg) // pod predkoscia regulatora dawka zadana - realfill = realfill; if (enrot < dizel_nreg_min && RList[mcp].R > 0.001) // jesli ponizej biegu jalowego i niezerowa dawka, to dawaj pelna realfill = 1; } @@ -9118,7 +9116,7 @@ bool TMoverParameters::readMPT0(std::string const &line) } if (true == parser.getTokens(1, false)) { - int autoswitch; + int autoswitch{0}; parser >> autoswitch; MotorParam[idx].AutoSwitch = autoswitch == 1; } @@ -9174,7 +9172,7 @@ bool TMoverParameters::readMPTElectricSeries(std::string const &line) parser >> MotorParam[idx].mfi >> MotorParam[idx].mIsat >> MotorParam[idx].fi >> MotorParam[idx].Isat; if (true == parser.getTokens(1, false)) { - int autoswitch; + int autoswitch{0}; parser >> autoswitch; MotorParam[idx].AutoSwitch = autoswitch == 1; } @@ -9223,7 +9221,7 @@ bool TMoverParameters::readMPTDieselEngine(std::string const &line) parser >> MotorParam[idx].mIsat >> MotorParam[idx].fi >> MotorParam[idx].mfi; if (true == parser.getTokens(1, false)) { - int autoswitch; + int autoswitch{0}; parser >> autoswitch; MotorParam[idx].AutoSwitch = autoswitch == 1; } @@ -11048,7 +11046,7 @@ void TMoverParameters::LoadFIZ_DCEMUED(std::string const &line) void TMoverParameters::LoadFIZ_SpringBrake(std::string const &line) { - double vol; + double vol{1.0}; extract_value(vol, "Volume", line, "1"); if (!SpringBrake.Cylinder) SpringBrake.Cylinder = std::make_shared(); diff --git a/McZapkie/hamulce.cpp b/McZapkie/hamulce.cpp index 44e8be4b..64a7fb85 100644 --- a/McZapkie/hamulce.cpp +++ b/McZapkie/hamulce.cpp @@ -3516,10 +3516,7 @@ double TMHZ_K5P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) LimCP = std::min(LimCP, HP); // pozycja + czasowy lub zasilanie ActFlowSpeed = 4; - if (LimCP > CP) // podwyzszanie szybkie - CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy; - else - CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy + CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy double uop = UnbrakeOverPressure; // unbrake over pressure in actual state ManualOvrldActive = UniversalFlag & TUniversalBrake::ub_HighPressure; // button is pressed @@ -3546,10 +3543,9 @@ double TMHZ_K5P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) else dpMainValve = PFVd(PP, 0, ActFlowSpeed / LBDelay, dpPipe, 0.4); - if ((EQ(i_bcp, -1) && AutoOvrld) || (i_bcp < 0.5 && UniversalFlag & TUniversalBrake::ub_Overload)) + if (((EQ(i_bcp, -1) && AutoOvrld) || (i_bcp < 0.5 && UniversalFlag & TUniversalBrake::ub_Overload)) && TP < OverloadMaxPressure) { - if (TP < OverloadMaxPressure) - TP = TP + 0.03 * dt; + TP = TP + 0.03 * dt; } if (EQ(i_bcp, 3)) @@ -3699,10 +3695,7 @@ double TMHZ_6P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) LimCP = std::min(LimCP, HP); // pozycja + czasowy lub zasilanie ActFlowSpeed = 4; - if (LimCP > CP) // podwyzszanie szybkie - CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy; - else - CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy + CP = CP + 9 * std::min(abs(LimCP - CP), 0.05) * PR(CP, LimCP) * dt; // zbiornik sterujacy dpPipe = std::min(HP, CP + TP + RedAdj); @@ -3729,10 +3722,9 @@ double TMHZ_6P::GetPF(double i_bcp, double PP, double HP, double dt, double ep) else dpMainValve = PFVd(PP, 0, ActFlowSpeed / LBDelay, dpPipe, 0.4); - if ((EQ(i_bcp, -1) && AutoOvrld) || (i_bcp < 0.5 && UniversalFlag & TUniversalBrake::ub_Overload)) + if (((EQ(i_bcp, -1) && AutoOvrld) || (i_bcp < 0.5 && UniversalFlag & TUniversalBrake::ub_Overload)) && TP < OverloadMaxPressure) { - if (TP < OverloadMaxPressure) - TP = TP + 0.03 * dt; + TP = TP + 0.03 * dt; } if (EQ(i_bcp, 4)) diff --git a/application/application.cpp b/application/application.cpp index 13fa8620..8dc2d0b3 100644 --- a/application/application.cpp +++ b/application/application.cpp @@ -498,7 +498,7 @@ int eu07_application::run() { command_queue::commands_map commands_to_exec; command_queue::commands_map local_commands = simulation::Commands.pop_intercept_queue(); - double slave_sync; + double slave_sync{0.0}; // if we're the server if (m_network && m_network->servers) diff --git a/application/drivermode.cpp b/application/drivermode.cpp index a79c31b0..d8581c42 100644 --- a/application/drivermode.cpp +++ b/application/drivermode.cpp @@ -690,7 +690,7 @@ void driver_mode::update_camera(double const Deltatime) // reset window state, it'll be set again if applicable in a check below Global.CabWindowOpen = false; - if (simulation::Train != nullptr && Camera.m_owner != nullptr && false == DebugCameraFlag) + if (simulation::Train != nullptr && controlled != nullptr && Camera.m_owner != nullptr && false == DebugCameraFlag) { // jeśli jazda w kabinie, przeliczyć trzeba parametry kamery /* diff --git a/application/scenarioloaderuilayer.cpp b/application/scenarioloaderuilayer.cpp index c4e7df3d..de0d4e64 100644 --- a/application/scenarioloaderuilayer.cpp +++ b/application/scenarioloaderuilayer.cpp @@ -213,7 +213,7 @@ void scenarioloader_ui::render_() const float spacing = 10.0f * scale; // odstęp między ikoną a tekstem // Loading icon - const deferred_image *img = &m_loading_wheel_frames[38]; + deferred_image *img = &m_loading_wheel_frames[38]; const auto loading_tex = img->get(); const auto loading_size = glm::vec2(img->size()) * glm::vec2(scale); diff --git a/application/uitranscripts.cpp b/application/uitranscripts.cpp index 96c9f735..c9780e10 100644 --- a/application/uitranscripts.cpp +++ b/application/uitranscripts.cpp @@ -42,7 +42,7 @@ TTranscripts::Add( std::string const &txt, bool backgorund ) { cParser parser( asciitext ); while( true == parser.getTokens( 3, false, "[]\n" ) ) { - float begin, end; + float begin{ 0.f }, end{ 0.f }; std::string transcript; parser >> begin diff --git a/audio/audiorenderer.cpp b/audio/audiorenderer.cpp index d0e1ce55..080ab66f 100644 --- a/audio/audiorenderer.cpp +++ b/audio/audiorenderer.cpp @@ -573,15 +573,12 @@ openal_renderer::init_caps() { if( ::alcIsExtensionPresent( nullptr, "ALC_ENUMERATION_EXT" ) == AL_TRUE ) { // enumeration supported WriteLog( "available audio devices:" ); - auto const *devices { ::alcGetString( nullptr, ALC_DEVICE_SPECIFIER ) }; - auto const - *device { devices }, - *next { devices + 1 }; - while( device && *device != '\0' && next && *next != '\0' ) { - WriteLog( { device } ); - auto const len { std::strlen( device ) }; - device += len + 1; - next += len + 2; + // device list is a sequence of null-terminated names, ending with an empty name + auto const *device { ::alcGetString( nullptr, ALC_DEVICE_SPECIFIER ) }; + while( device != nullptr && *device != '\0' ) { + std::string const devicename { device }; + WriteLog( devicename ); + device += devicename.size() + 1; } } diff --git a/launcher/deferred_image.h b/launcher/deferred_image.h index fcd25fd6..85582264 100644 --- a/launcher/deferred_image.h +++ b/launcher/deferred_image.h @@ -1,10 +1,15 @@ #pragma once +#include + #include "model/Texture.h" #include "rendering/renderer.h" class deferred_image { public: + // value returned by get() when the image isn't (yet) available + static constexpr GLuint invalid_id { std::numeric_limits::max() }; + deferred_image() = default; deferred_image(const std::string &p) : path(p) { } deferred_image(const deferred_image&) = delete; @@ -15,7 +20,8 @@ public: return image != null_handle || !path.empty(); } - GLuint get() const + // loads the texture on first use + GLuint get() { if (!path.empty()) { image = GfxRenderer->Fetch_Texture(path, true); @@ -30,7 +36,7 @@ public: return tex.get_id(); } - return -1; + return invalid_id; } glm::ivec2 size() const @@ -43,6 +49,6 @@ public: } private: - mutable std::string path; - mutable texture_handle image = 0; + std::string path; + texture_handle image = 0; }; diff --git a/launcher/scenery_list.cpp b/launcher/scenery_list.cpp index a24d3cf9..0baf2f05 100644 --- a/launcher/scenery_list.cpp +++ b/launcher/scenery_list.cpp @@ -285,7 +285,7 @@ void ui::scenerylist_panel::draw_trainset(trainset_desc &trainset) for (auto &dyn_desc : trainset.vehicles) { deferred_image *mini = nullptr; - if (dyn_desc.skin && dyn_desc.skin->mini.get() != (GLuint)-1) + if (dyn_desc.skin && dyn_desc.skin->mini.get() != deferred_image::invalid_id) mini = &dyn_desc.skin->mini; else mini = &placeholder_mini; diff --git a/launcher/vehicle_picker.cpp b/launcher/vehicle_picker.cpp index f7d3503f..9fe0a29a 100644 --- a/launcher/vehicle_picker.cpp +++ b/launcher/vehicle_picker.cpp @@ -37,7 +37,7 @@ void ui::vehiclepicker_panel::render_contents() if (ImGui::RadioButton(STR_C("texture group"), display_by_groups)) display_by_groups = true; - std::vector skinset_list; + std::vector skinset_list; if (display_by_groups) { std::vector model_list; @@ -114,7 +114,7 @@ void ui::vehiclepicker_panel::render_contents() for (int i = clipper.DisplayStart; i < clipper.DisplayEnd; i++) { auto &desc = model_list[i]; - const deferred_image *image = nullptr; + deferred_image *image = nullptr; for (auto const &skinset : desc->matching_skinsets) { if (skinset->mini) { image = &skinset->mini; @@ -256,7 +256,7 @@ bool ui::vehiclepicker_panel::skin_filter(const skin_set *skin, std::vectorget(); - if (tex != (GLuint)-1) { + if (tex != deferred_image::invalid_id) { glm::ivec2 size = image->size(); float width = 30.0f / size.y * size.x; ImGui::SameLine(ImGui::GetContentRegionAvail().x - width); diff --git a/launcher/vehicle_picker.h b/launcher/vehicle_picker.h index ad6e6dc2..ffdfb2a4 100644 --- a/launcher/vehicle_picker.h +++ b/launcher/vehicle_picker.h @@ -14,7 +14,7 @@ class vehiclepicker_panel : public ui_panel void render_contents() override; private: - bool selectable_image(const char *desc, bool selected, const deferred_image *image, const skin_set *pickable = nullptr); + bool selectable_image(const char *desc, bool selected, deferred_image *image, const skin_set *pickable = nullptr); vehicle_type selected_type = vehicle_type::none; std::shared_ptr selected_vehicle; diff --git a/model/AnimModel.cpp b/model/AnimModel.cpp index cf6df321..770790c1 100644 --- a/model/AnimModel.cpp +++ b/model/AnimModel.cpp @@ -646,11 +646,11 @@ void TAnimModel::on_season_update() { void TAnimModel::RaPrepare() { // ustawia światła i animacje we wzorcu modelu przed renderowaniem egzemplarza - bool state; // stan światła if (Global.UpdateMaterials) on_season_update(); for (int i = 0; i < iNumLights; ++i) { + bool state{ false }; // stan światła auto const lightmode { static_cast( std::abs( lsLights[ i ] ) ) }; switch( lightmode ) { case ls_On: diff --git a/model/Model3d.cpp b/model/Model3d.cpp index 91860cdf..66fc08c9 100644 --- a/model/Model3d.cpp +++ b/model/Model3d.cpp @@ -17,6 +17,7 @@ Copyright (C) 2001-2004 Marcin Wozniak, Maciej Czapkiewicz and others #include "utilities/Globals.h" #include "utilities/Logs.h" +#include "utilities/utilities.h" #include "rendering/renderer.h" #include "utilities/Timer.h" #include "simulation/simulation.h" @@ -55,8 +56,6 @@ TSubModel::~TSubModel() SafeDelete(Child); delete fMatrix; // własny transform trzeba usunąć (zawsze jeden) } - delete[] smLetter; // używany tylko roboczo dla TP_TEXT, do przyspieszenia - // wyświetlania }; void TSubModel::Name_Material(std::string const &Name) @@ -892,6 +891,67 @@ void TSubModel::InitialRotate(bool doit) Next->InitialRotate(doit); }; +// text display (TP_TEXT): returns sub-models of the characters to draw for provided text, in drawing order. +// a character is matched with the first character of a child sub-model name. both the text and the names can be +// encoded either in utf-8 or in windows-1250, so the legacy (ansi) scenery and model files work along the utf-8 ones +std::vector const &TSubModel::text_letters(std::string const &Text) +{ + // the sequence is prepared once for each text the display shows. the texts of the model instances don't change, + // so drawing a display afterwards costs a single lookup, however many instances and passes take turns + auto const prepared{m_textlayouts.find(Text)}; + if (prepared != m_textlayouts.end()) + { + return prepared->second; + } + + std::u32string characters; + if (false == m_lettersready) + { + for (auto *letter = Child; letter != nullptr; letter = letter->Next) + { + if (letter->pName.empty()) + { + continue; + } + if (false == utf8_to_utf32(letter->pName, characters)) + { + win1250_to_utf32(letter->pName, characters); + } + if (characters.front() != 0) + { + m_letters[characters.front()] = letter; + } + } + m_lettersready = true; + } + + // the text is taken for utf-8 if it's valid as such, and for windows-1250 otherwise. a short ansi text can pass + // for utf-8 by accident (two upper case accented letters in a row are enough for this to happen), + // so if both readings are possible the one which finds more of its characters in the model wins + auto const known = [this](std::u32string const &Characters) { + return std::ranges::count_if(Characters, [this](char32_t const Character) { return m_letters.contains(Character); }); + }; + std::u32string ansi; + win1250_to_utf32(Text, ansi); + if (false == utf8_to_utf32(Text, characters) || known(ansi) > known(characters)) + { + characters = ansi; + } + + auto &textletters{m_textlayouts[Text]}; + // NOTE: the first character of the text isn't displayed. the displays always worked this way, and the existing + // content relies on it + for (std::size_t idx = 1; idx < characters.size(); ++idx) + { + auto const lookup{m_letters.find(characters[idx])}; + if (lookup != m_letters.end()) + { + textletters.emplace_back(lookup->second); + } + } + return textletters; +} + void TSubModel::ChildAdd(TSubModel *SubModel) { // dodanie submodelu potemnego (uzależnionego) // Ra: zmiana kolejności, żeby kolejne móc renderować po aktualnym (było @@ -1469,7 +1529,9 @@ void TSubModel::ParentMatrix(float4x4 *m) const void TSubModel::ReplaceMatrix(const glm::mat4 &mat) { - *fMatrix = float4x4(glm::value_ptr(mat)); + for (int column = 0; column < 4; ++column) + for (int row = 0; row < 4; ++row) + fMatrix->e[column * 4 + row] = mat[column][row]; } void TSubModel::ReplaceMaterial(const std::string &name) @@ -1558,13 +1620,8 @@ TSubModel *TModel3d::GetFromName(std::string const &Name) const { // wyszukanie submodelu po nazwie if (Name.empty()) return Root; // potrzebne do terenu z E3D - if (iFlags & 0x0200) // wczytany z pliku tekstowego, wyszukiwanie rekurencyjne - return Root ? Root->GetFromName(Name) : nullptr; - else // wczytano z pliku binarnego, można wyszukać iteracyjnie - { - // for (int i=0;iGetFromName(Name) : nullptr; - } + // both text and binary models are searched recursively + return Root ? Root->GetFromName(Name) : nullptr; }; // returns submodels which scenery instances of the model refer to by name, locating them on first use diff --git a/model/Model3d.h b/model/Model3d.h index c8c2fc9a..27565c20 100644 --- a/model/Model3d.h +++ b/model/Model3d.h @@ -156,7 +156,10 @@ public: // chwilowo std::uintptr_t iAnimOwner{ 0 }; // roboczy numer egzemplarza, który ustawił animację TAnimType b_aAnim{ TAnimType::at_None }; // kody animacji oddzielnie, bo zerowane std::shared_ptr mAnimMatrix; // macierz do animacji kwaternionowych - TSubModel **smLetter{ nullptr }; // wskaźnik na tablicę submdeli do generoania tekstu (docelowo zapisać do E3D) + // text display (TP_TEXT) data, filled on first use + std::unordered_map m_letters; // sub-models of the available characters, by unicode code point + bool m_lettersready { false }; // whether the character lookup was built yet + std::unordered_map> m_textlayouts; // sub-models of consecutive characters, for each text shown TSubModel *Parent{ nullptr }; // nadrzędny, np. do wymnażania macierzy int iVisible { 1 }; // roboczy stan widoczności float fVisible { 1.f }; // visibility level @@ -202,6 +205,8 @@ public: void SetTranslate(float3 vNewTransVector); void SetRotateIK1(float3 vNewAngles); TSubModel * GetFromName( std::string const &search, bool i = true ); + // text display (TP_TEXT): returns sub-models of the characters to draw for provided text, in drawing order + std::vector const & text_letters( std::string const &Text ); inline float4x4 * GetMatrix() { return fMatrix; }; inline float4x4 const * GetMatrix() const { return fMatrix; }; // returns offset vector from root diff --git a/model/Texture.cpp b/model/Texture.cpp index a3508168..27a8d68c 100644 --- a/model/Texture.cpp +++ b/model/Texture.cpp @@ -524,12 +524,12 @@ opengl_texture::make_from_memory(size_t width, size_t height, const uint8_t *raw void opengl_texture::update_from_memory(size_t width, size_t height, const uint8_t *raw) { - if (id != (GLuint)-1 && (width != data_width || height != data_height || GL_SRGB8_ALPHA8 != data_format || GL_RGBA != data_components)) + if (id != invalid_id && (width != data_width || height != data_height || GL_SRGB8_ALPHA8 != data_format || GL_RGBA != data_components)) { glDeleteTextures(1, &id); - id = (GLuint)-1; + id = invalid_id; } - if (id == (GLuint)-1) + if (id == invalid_id) { data_width = width; data_height = height; diff --git a/model/Texture.h b/model/Texture.h index 3df04fef..0a4ae62e 100644 --- a/model/Texture.h +++ b/model/Texture.h @@ -10,6 +10,7 @@ http://mozilla.org/MPL/2.0/. #pragma once #include +#include #include "winheaders.h" #include #include "model/ResourceManager.h" @@ -86,7 +87,8 @@ struct opengl_texture : public ITexture { virtual void update_from_memory(size_t width, size_t height, const uint8_t *data) override; // members - GLuint id{ (GLuint)-1 }; // associated GL resource + static constexpr GLuint invalid_id { std::numeric_limits::max() }; // id value of texture without GL resource + GLuint id{ invalid_id }; // associated GL resource bool has_alpha{ false }; // indicates the texture has alpha channel bool is_ready{ false }; // indicates the texture was processed and is ready for use std::string traits; // requested texture attributes: wrapping modes etc diff --git a/network/message.h b/network/message.h index 871e833f..5aa07d78 100644 --- a/network/message.h +++ b/network/message.h @@ -30,17 +30,17 @@ struct client_hello : public message virtual void serialize(std::ostream &stream) const override; virtual void deserialize(std::istream &stream) override; - int32_t version{}; - uint32_t start_packet{}; + int32_t version{0}; + uint32_t start_packet{0}; }; struct server_hello : public message { server_hello() : message(SERVER_HELLO) {} - uint32_t seed{}; - int64_t timestamp{}; - int64_t config{}; + uint32_t seed{0}; + int64_t timestamp{0}; + int64_t config{0}; std::string scenario; virtual void serialize(std::ostream &stream) const override; @@ -62,9 +62,9 @@ struct frame_info : public request_command { frame_info() : request_command(FRAME_INFO) {} - double render_dt{}; - double dt{}; - double sync{}; + double render_dt{0.0}; + double dt{0.0}; + double sync{0.0}; virtual void serialize(std::ostream &stream) const override; virtual void deserialize(std::istream &stream) override; diff --git a/rendering/frustum.cpp b/rendering/frustum.cpp index afe16441..f4677065 100644 --- a/rendering/frustum.cpp +++ b/rendering/frustum.cpp @@ -22,30 +22,14 @@ cFrustum::calculate() { void cFrustum::calculate( glm::mat4 const &Projection, glm::mat4 const &Modelview ) { - float const *proj = glm::value_ptr( Projection ); - float const *modl = glm::value_ptr( Modelview ); + // multiply the matrices to retrieve clipping planes (column-major, same layout as the old float[16] form) + auto const clipmatrix { Projection * Modelview }; float clip[ 16 ]; - - // multiply the matrices to retrieve clipping planes - clip[ 0 ] = modl[ 0 ] * proj[ 0 ] + modl[ 1 ] * proj[ 4 ] + modl[ 2 ] * proj[ 8 ] + modl[ 3 ] * proj[ 12 ]; - clip[ 1 ] = modl[ 0 ] * proj[ 1 ] + modl[ 1 ] * proj[ 5 ] + modl[ 2 ] * proj[ 9 ] + modl[ 3 ] * proj[ 13 ]; - clip[ 2 ] = modl[ 0 ] * proj[ 2 ] + modl[ 1 ] * proj[ 6 ] + modl[ 2 ] * proj[ 10 ] + modl[ 3 ] * proj[ 14 ]; - clip[ 3 ] = modl[ 0 ] * proj[ 3 ] + modl[ 1 ] * proj[ 7 ] + modl[ 2 ] * proj[ 11 ] + modl[ 3 ] * proj[ 15 ]; - - clip[ 4 ] = modl[ 4 ] * proj[ 0 ] + modl[ 5 ] * proj[ 4 ] + modl[ 6 ] * proj[ 8 ] + modl[ 7 ] * proj[ 12 ]; - clip[ 5 ] = modl[ 4 ] * proj[ 1 ] + modl[ 5 ] * proj[ 5 ] + modl[ 6 ] * proj[ 9 ] + modl[ 7 ] * proj[ 13 ]; - clip[ 6 ] = modl[ 4 ] * proj[ 2 ] + modl[ 5 ] * proj[ 6 ] + modl[ 6 ] * proj[ 10 ] + modl[ 7 ] * proj[ 14 ]; - clip[ 7 ] = modl[ 4 ] * proj[ 3 ] + modl[ 5 ] * proj[ 7 ] + modl[ 6 ] * proj[ 11 ] + modl[ 7 ] * proj[ 15 ]; - - clip[ 8 ] = modl[ 8 ] * proj[ 0 ] + modl[ 9 ] * proj[ 4 ] + modl[ 10 ] * proj[ 8 ] + modl[ 11 ] * proj[ 12 ]; - clip[ 9 ] = modl[ 8 ] * proj[ 1 ] + modl[ 9 ] * proj[ 5 ] + modl[ 10 ] * proj[ 9 ] + modl[ 11 ] * proj[ 13 ]; - clip[ 10 ] = modl[ 8 ] * proj[ 2 ] + modl[ 9 ] * proj[ 6 ] + modl[ 10 ] * proj[ 10 ] + modl[ 11 ] * proj[ 14 ]; - clip[ 11 ] = modl[ 8 ] * proj[ 3 ] + modl[ 9 ] * proj[ 7 ] + modl[ 10 ] * proj[ 11 ] + modl[ 11 ] * proj[ 15 ]; - - clip[ 12 ] = modl[ 12 ] * proj[ 0 ] + modl[ 13 ] * proj[ 4 ] + modl[ 14 ] * proj[ 8 ] + modl[ 15 ] * proj[ 12 ]; - clip[ 13 ] = modl[ 12 ] * proj[ 1 ] + modl[ 13 ] * proj[ 5 ] + modl[ 14 ] * proj[ 9 ] + modl[ 15 ] * proj[ 13 ]; - clip[ 14 ] = modl[ 12 ] * proj[ 2 ] + modl[ 13 ] * proj[ 6 ] + modl[ 14 ] * proj[ 10 ] + modl[ 15 ] * proj[ 14 ]; - clip[ 15 ] = modl[ 12 ] * proj[ 3 ] + modl[ 13 ] * proj[ 7 ] + modl[ 14 ] * proj[ 11 ] + modl[ 15 ] * proj[ 15 ]; + for( int column = 0; column < 4; ++column ) { + for( int row = 0; row < 4; ++row ) { + clip[ column * 4 + row ] = clipmatrix[ column ][ row ]; + } + } // get the sides of the frustum. m_frustum[ side_RIGHT ][ plane_A ] = clip[ 3 ] - clip[ 0 ]; diff --git a/rendering/lightarray.h b/rendering/lightarray.h index 670cbea8..d7459fe9 100644 --- a/rendering/lightarray.h +++ b/rendering/lightarray.h @@ -18,7 +18,7 @@ public: int index{ -1 }; // 0: front lights, 1: rear lights glm::dvec3 position; // position of the light in 3d scene glm::vec3 direction; // direction of the light in 3d scene - glm::vec3 color{ 255.0f / 255.0f, 241.0f / 255.0f, 224.0f / 255.0f }; // color of the light, default is halogen light + glm::vec3 color{ 1.0f, 241.0f / 255.0f, 224.0f / 255.0f }; // color of the light, default is halogen light float intensity{ 0.0f }; // (combined) intensity of the light(s) int count{ 0 }; // number (or pattern) of active light(s) glm::vec3 state{ 0.f }; // state of individual lights diff --git a/rendering/opengl33renderer.cpp b/rendering/opengl33renderer.cpp index 392404a1..36bf4572 100644 --- a/rendering/opengl33renderer.cpp +++ b/rendering/opengl33renderer.cpp @@ -29,6 +29,23 @@ http://mozilla.org/MPL/2.0/. int constexpr EU07_PICKBUFFERSIZE{ 1024 }; // size of (square) textures bound with the pick framebuffer int constexpr EU07_REFLECTIONFIDELITYOFFSET { 250 }; // artificial increase of range for reflection pass detail reduction +namespace { + +// returns material assigned to the submodel, resolving replacable skins. null_handle if no skin set is bound +material_handle +submodel_material( TSubModel const *Submodel ) { + + if( Submodel->m_material >= 0 ) { + return Submodel->m_material; + } + return ( + TSubModel::ReplacableSkinId != nullptr ? + TSubModel::ReplacableSkinId[ -Submodel->m_material ] : + null_handle ); +} + +} // namespace + void GLAPIENTRY ErrorCallback( GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length, const GLchar* message, const void* userParam ) { /* @@ -1975,7 +1992,7 @@ bool opengl33_renderer::Render(world_environment *Environment) auto const fogfactor{std::clamp(Global.fFogEnd / 2000.f, 0.f, 1.f)}; // stronger fog reduces opacity of the celestial bodies float const duskfactor = 1.0f - std::clamp(std::abs(Environment->m_sun.getAngle()), 0.0f, 12.0f) / 12.0f; - glm::vec3 suncolor = glm::mix(glm::vec3(255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor); + glm::vec3 suncolor = glm::mix(glm::vec3(1.0f, 242.0f / 255.0f, 231.0f / 255.0f), glm::vec3(235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f), duskfactor); // sun { @@ -2381,8 +2398,7 @@ opengl_material &opengl33_renderer::Material(material_handle const Material) opengl_material const & opengl33_renderer::Material( TSubModel const * Submodel ) const { - auto const material { Submodel->m_material >= 0 ? Submodel->m_material : Submodel->ReplacableSkinId[ -Submodel->m_material ] }; - return m_materials.material( material ); + return m_materials.material( submodel_material( Submodel ) ); } texture_handle opengl33_renderer::Fetch_Texture(std::string const &Filename, bool const Loadnow, GLint format_hint) @@ -4008,7 +4024,7 @@ void opengl33_renderer::Render(TSubModel *Submodel) // textures... if (Submodel->m_material < 0) { // zmienialne skóry - Bind_Material(Submodel->ReplacableSkinId[-Submodel->m_material], Submodel); + Bind_Material(submodel_material(Submodel), Submodel); } else { @@ -4045,7 +4061,7 @@ void opengl33_renderer::Render(TSubModel *Submodel) if (Submodel->m_material < 0) { // zmienialne skóry - Bind_Material_Shadow(Submodel->ReplacableSkinId[-Submodel->m_material]); + Bind_Material_Shadow(submodel_material(Submodel)); } else { @@ -4964,6 +4980,21 @@ bool opengl33_renderer::Render_Alpha(TModel3d *Model, material_data const *Mater return result; } +// characters of a text display, laid out in a row. the text can be encoded in utf-8 or in windows-1250 +void opengl33_renderer::Render_Alpha_text(TSubModel *Submodel) +{ + if (TSubModel::pasText == nullptr) + { + return; + } + for (auto *p : Submodel->text_letters(*TSubModel::pasText)) + { // translucent only, for the time being + Render_Alpha(p); + if (p->fMatrix) + ::glMultMatrixf(p->fMatrix->readArray()); // move on to the place of the next character + } +} + void opengl33_renderer::Render_Alpha(TSubModel *Submodel) { // renderowanie przezroczystych przez DL @@ -5004,7 +5035,7 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) // textures... if (Submodel->m_material < 0) { // zmienialne skóry - Bind_Material(Submodel->ReplacableSkinId[-Submodel->m_material], Submodel); + Bind_Material(submodel_material(Submodel), Submodel); } else { @@ -5036,7 +5067,7 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) if (Submodel->m_material < 0) { // zmienialne skóry - Bind_Material_Shadow(Submodel->ReplacableSkinId[-Submodel->m_material]); + Bind_Material_Shadow(submodel_material(Submodel)); } else { @@ -5192,29 +5223,7 @@ void opengl33_renderer::Render_Alpha(TSubModel *Submodel) { if (Submodel->eType == TP_TEXT) { // tekst renderujemy w specjalny sposób, zamiast submodeli z łańcucha Child - int i, j = (int)Submodel->pasText->size(); - TSubModel *p; - if (!Submodel->smLetter) - { // jeśli nie ma tablicy, to ją stworzyć; miejsce nieodpowiednie, ale tymczasowo może być - Submodel->smLetter = new TSubModel *[256]; // tablica wskaźników submodeli dla wyświetlania tekstu - memset(Submodel->smLetter, 0, 256 * sizeof(TSubModel *)); // wypełnianie zerami - p = Submodel->Child; - while (p) - { - Submodel->smLetter[p->pName[0]] = p; - p = p->Next; // kolejny znak - } - } - for (i = 1; i <= j; ++i) - { - p = Submodel->smLetter[(*(Submodel->pasText))[i]]; // znak do wyświetlenia - if (p) - { // na razie tylko jako przezroczyste - Render_Alpha(p); - if (p->fMatrix) - ::glMultMatrixf(p->fMatrix->readArray()); // przesuwanie widoku - } - } + Render_Alpha_text(Submodel); } else if (Submodel->iAlpha & Submodel->iFlags & 0x002F0000) Render_Alpha(Submodel->Child); diff --git a/rendering/opengl33renderer.h b/rendering/opengl33renderer.h index 1820abe4..90142cd1 100644 --- a/rendering/opengl33renderer.h +++ b/rendering/opengl33renderer.h @@ -322,6 +322,7 @@ class opengl33_renderer : public gfx_renderer { bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance, glm::dvec3 const &Position, glm::vec3 const &Angle); bool Render_Alpha(TModel3d *Model, material_data const *Material, float const Squaredistance); void Render_Alpha(TSubModel *Submodel); + void Render_Alpha_text(TSubModel *Submodel); // characters of a text display void Update_Lights(light_array &Lights); glm::vec3 pick_color(std::size_t const Index); std::size_t pick_index(glm::ivec3 const &Color); diff --git a/rendering/openglrenderer.cpp b/rendering/openglrenderer.cpp index dcbd1df2..27c94da8 100644 --- a/rendering/openglrenderer.cpp +++ b/rendering/openglrenderer.cpp @@ -31,6 +31,23 @@ int constexpr EU07_PICKBUFFERSIZE { 1024 }; // size of (square) textures bound w int constexpr EU07_ENVIRONMENTBUFFERSIZE { 256 }; // size of (square) environmental cube map texture int constexpr EU07_REFLECTIONFIDELITYOFFSET { 250 }; // artificial increase of range for reflection pass detail reduction +namespace { + +// returns material assigned to the submodel, resolving replacable skins. null_handle if no skin set is bound +material_handle +submodel_material( TSubModel const *Submodel ) { + + if( Submodel->m_material >= 0 ) { + return Submodel->m_material; + } + return ( + TSubModel::ReplacableSkinId != nullptr ? + TSubModel::ReplacableSkinId[ -Submodel->m_material ] : + null_handle ); +} + +} // namespace + float const EU07_OPACITYDEFAULT { 0.5f }; bool @@ -1571,7 +1588,7 @@ opengl_renderer::Render( world_environment *Environment ) { auto const fogfactor { std::clamp( Global.fFogEnd / 2000.f, 0.f, 1.f ) }; // closer/denser fog reduces opacity of the celestial bodies float const duskfactor = 1.0f - std::clamp( std::abs( Environment->m_sun.getAngle() ), 0.0f, 12.0f ) / 12.0f; glm::vec3 suncolor = glm::mix( - glm::vec3( 255.0f / 255.0f, 242.0f / 255.0f, 231.0f / 255.0f ), + glm::vec3( 1.0f, 242.0f / 255.0f, 231.0f / 255.0f ), glm::vec3( 235.0f / 255.0f, 140.0f / 255.0f, 36.0f / 255.0f ), duskfactor ); // sun @@ -1790,8 +1807,7 @@ opengl_renderer::Material( material_handle const Material ) const { opengl_material const & opengl_renderer::Material( TSubModel const * Submodel ) const { - auto const material { Submodel->m_material >= 0 ? Submodel->m_material : Submodel->ReplacableSkinId[ -Submodel->m_material ] }; - return m_materials.material( material ); + return m_materials.material( submodel_material( Submodel ) ); } // shader methods @@ -2768,10 +2784,7 @@ opengl_renderer::Render( TSubModel *Submodel ) { } #endif // material configuration: - auto const material{ ( - Submodel->m_material < 0 ? - Submodel->ReplacableSkinId[ -Submodel->m_material ] : // zmienialne skóry - Submodel->m_material ) }; // również 0 + auto const material{ submodel_material( Submodel ) }; // zmienialne skóry, również 0 // textures... Bind_Material( material ); // ...colors and opacity... @@ -2879,7 +2892,7 @@ opengl_renderer::Render( TSubModel *Submodel ) { // material configuration: // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry - Bind_Material( TSubModel::ReplacableSkinId[ -Submodel->m_material ], Submodel ); + Bind_Material( submodel_material( Submodel ), Submodel ); } else { // również 0 @@ -2897,7 +2910,7 @@ opengl_renderer::Render( TSubModel *Submodel ) { ::glColor3fv( glm::value_ptr( pick_color( m_pickcontrolsitems.size() ) ) ); // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry - Bind_Material( TSubModel::ReplacableSkinId[ -Submodel->m_material ], Submodel ); + Bind_Material( submodel_material( Submodel ), Submodel ); } else { // również 0 @@ -2971,10 +2984,10 @@ opengl_renderer::Render( TSubModel *Submodel ) { auto const unitstate = m_unitstate; switch_units( m_unitstate.diffuse, false, false ); - auto const *lightcolor { + glm::vec3 const lightcolor { Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override - glm::value_ptr( Submodel->f4Diffuse ) : - glm::value_ptr( Submodel->DiffuseOverride ) }; + glm::vec3( Submodel->f4Diffuse ) : + Submodel->DiffuseOverride }; // main draw call if( Global.Overcast > 1.f ) { @@ -2987,9 +3000,9 @@ opengl_renderer::Render( TSubModel *Submodel ) { ::glPointSize( pointsize * fogfactor ); ::glColor4f( - lightcolor[ 0 ], - lightcolor[ 1 ], - lightcolor[ 2 ], + lightcolor.r, + lightcolor.g, + lightcolor.b, Submodel->fVisible * std::min( 1.f, lightlevel ) * 0.5f ); ::glDepthMask( GL_FALSE ); m_geometry.draw( Submodel->m_geometry.handle ); @@ -2997,9 +3010,9 @@ opengl_renderer::Render( TSubModel *Submodel ) { } ::glPointSize( pointsize ); ::glColor4f( - lightcolor[ 0 ], - lightcolor[ 1 ], - lightcolor[ 2 ], + lightcolor.r, + lightcolor.g, + lightcolor.b, Submodel->fVisible * std::min( 1.f, lightlevel ) ); m_geometry.draw( Submodel->m_geometry.handle ); @@ -3800,6 +3813,19 @@ opengl_renderer::Render_Alpha( TModel3d *Model, material_data const *Material, f return result; } +// characters of a text display, laid out in a row. the text can be encoded in utf-8 or in windows-1250 +void +opengl_renderer::Render_Alpha_text( TSubModel *Submodel ) { + + if( TSubModel::pasText == nullptr ) { return; } + + for( auto *p : Submodel->text_letters( *TSubModel::pasText ) ) { // translucent only, for the time being + Render_Alpha( p ); + if( p->fMatrix ) + ::glMultMatrixf( p->fMatrix->readArray() ); // move on to the place of the next character + } +} + void opengl_renderer::Render_Alpha( TSubModel *Submodel ) { // renderowanie przezroczystych przez DL @@ -3845,10 +3871,7 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { #endif // material configuration: // textures... - auto const material { ( - Submodel->m_material < 0 ? - Submodel->ReplacableSkinId[ -Submodel->m_material ] : // zmienialne skóry - Submodel->m_material ) }; // również 0 + auto const material { submodel_material( Submodel ) }; // zmienialne skóry, również 0 // textures... Bind_Material( material ); // ...colors and opacity... @@ -3923,7 +3946,7 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { // material configuration: // textures... if( Submodel->m_material < 0 ) { // zmienialne skóry - Bind_Material( TSubModel::ReplacableSkinId[ -Submodel->m_material ], Submodel ); + Bind_Material( submodel_material( Submodel ), Submodel ); } else { // również 0 @@ -3986,14 +4009,14 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { auto const unitstate = m_unitstate; switch_units( unitstate.diffuse, false, false ); - auto const *lightcolor { + glm::vec3 const lightcolor { Submodel->DiffuseOverride.r < 0.f ? // -1 indicates no override - glm::value_ptr( Submodel->f4Diffuse ) : - glm::value_ptr( Submodel->DiffuseOverride ) }; + glm::vec3( Submodel->f4Diffuse ) : + Submodel->DiffuseOverride }; ::glColor4f( - lightcolor[ 0 ], - lightcolor[ 1 ], - lightcolor[ 2 ], + lightcolor.r, + lightcolor.g, + lightcolor.b, Submodel->fVisible * glarelevel ); // main draw call @@ -4018,25 +4041,7 @@ opengl_renderer::Render_Alpha( TSubModel *Submodel ) { if( Submodel->Child != nullptr ) { if( Submodel->eType == TP_TEXT ) { // tekst renderujemy w specjalny sposób, zamiast submodeli z łańcucha Child - int i, j = (int)Submodel->pasText->size(); - TSubModel *p; - if( !Submodel->smLetter ) { // jeśli nie ma tablicy, to ją stworzyć; miejsce nieodpowiednie, ale tymczasowo może być - Submodel->smLetter = new TSubModel *[ 256 ]; // tablica wskaźników submodeli dla wyświetlania tekstu - std::memset(Submodel->smLetter, 0, 256 * sizeof( TSubModel * )); // wypełnianie zerami - p = Submodel->Child; - while( p ) { - Submodel->smLetter[ p->pName[ 0 ] ] = p; - p = p->Next; // kolejny znak - } - } - for( i = 1; i <= j; ++i ) { - p = Submodel->smLetter[ ( *( Submodel->pasText) )[ i ] ]; // znak do wyświetlenia - if( p ) { // na razie tylko jako przezroczyste - Render_Alpha( p ); - if( p->fMatrix ) - ::glMultMatrixf( p->fMatrix->readArray() ); // przesuwanie widoku - } - } + Render_Alpha_text( Submodel ); } else if( Submodel->iAlpha & Submodel->iFlags & 0x002F0000 ) Render_Alpha( Submodel->Child ); diff --git a/rendering/openglrenderer.h b/rendering/openglrenderer.h index 820deb92..83dca483 100644 --- a/rendering/openglrenderer.h +++ b/rendering/openglrenderer.h @@ -270,6 +270,9 @@ private: Render_Alpha( TModel3d *Model, material_data const *Material, float const Squaredistance ); void Render_Alpha( TSubModel *Submodel ); + // characters of a text display + void + Render_Alpha_text( TSubModel *Submodel ); void Update_Lights( light_array &Lights ); bool diff --git a/rendering/precipitation.cpp b/rendering/precipitation.cpp index 3f9ecaec..02baaada 100644 --- a/rendering/precipitation.cpp +++ b/rendering/precipitation.cpp @@ -31,7 +31,7 @@ basic_precipitation::init() { void basic_precipitation::update() { - auto const timedelta { static_cast( ( DebugModeFlag ? Timer::GetDeltaTime() : Timer::GetDeltaTime() ) ) }; + auto const timedelta { static_cast( Timer::GetDeltaTime() ) }; if( timedelta == 0.0 ) { return; } diff --git a/simulation/simulation.cpp b/simulation/simulation.cpp index 3cbe6c69..70f9e307 100644 --- a/simulation/simulation.cpp +++ b/simulation/simulation.cpp @@ -148,6 +148,11 @@ state_manager::update_scripting_interface() { auto *time{ Memory.find( "__simulation.time" ) }; auto *date{ Memory.find( "__simulation.date" ) }; + if( weather == nullptr || time == nullptr || date == nullptr ) { + // scripting interface cells weren't created, nothing to synchronize + return; + } + if( simulation::is_ready ) { // potentially adjust weather if( weather->Value1() != m_scriptinginterface.weather->Value1() ) { @@ -407,7 +412,6 @@ void state_manager::process_commands() { pantograph.fLenL1 * std::sin( pantograph.fAngleL ) + pantograph.fLenU1 * std::sin( pantograph.fAngleU ) + pantograph.fHeight; - vehicle->MoverParameters->EnginePowerSource.CollectorParameters.CollectorsNo; } vehicle = vehicle->Prev(); diff --git a/simulation/simulationenvironment.cpp b/simulation/simulationenvironment.cpp index b96cea20..6c4061c4 100644 --- a/simulation/simulationenvironment.cpp +++ b/simulation/simulationenvironment.cpp @@ -78,7 +78,6 @@ world_environment::compute_weather() { Global.Overcast <= 0.70 ? 6 : Global.Overcast <= 0.80 ? 7 : Global.Overcast <= 0.90 ? 8 : - Global.Overcast > 0.90 ? 9 : 9; } diff --git a/utilities/Logs.cpp b/utilities/Logs.cpp index 737d25d7..5b4d1d9a 100644 --- a/utilities/Logs.cpp +++ b/utilities/Logs.cpp @@ -80,29 +80,20 @@ std::mutex logMutex; void LogService() { - // prevent crash if mutex is not initialized - while (true) - { - try - { - logMutex.lock(); - break; - } - catch (...) {} - } - logMutex.unlock(); - while (!Global.applicationQuitOrder) { { // --- Obsługa InfoStack --- while (!InfoStack.empty()) { - logMutex.lock(); - std::string msg = InfoStack.front().first; - bool isError = InfoStack.front().second; - InfoStack.pop_front(); - logMutex.unlock(); + std::string msg; + bool isError; + { + std::scoped_lock lock(logMutex); + msg = InfoStack.front().first; + isError = InfoStack.front().second; + InfoStack.pop_front(); + } // log to file if (Global.iWriteLogEnabled & 1) @@ -134,10 +125,12 @@ void LogService() // --- Obsługa ErrorStack --- while (!ErrorStack.empty()) { - logMutex.lock(); - std::string msg = ErrorStack.front(); - ErrorStack.pop_front(); - logMutex.unlock(); + std::string msg; + { + std::scoped_lock lock(logMutex); + msg = ErrorStack.front(); + ErrorStack.pop_front(); + } if (!(Global.iWriteLogEnabled & 1)) continue; diff --git a/utilities/parser.cpp b/utilities/parser.cpp index c8410e37..5c4ed31b 100644 --- a/utilities/parser.cpp +++ b/utilities/parser.cpp @@ -426,10 +426,9 @@ void cParser::stripFirstTokenBOM(std::string& token, bool ToLower, const char* B } // if first "token" was standalone BOM, read the next real token (avoid recursion) - while (token.empty() && hasChar()) { - readToken(token, ToLower, Break); + if (token.empty() && hasChar()) { // readToken will not re-enter BOM stripping because mFirstToken is now false - break; + readToken(token, ToLower, Break); } } diff --git a/utilities/parser.h b/utilities/parser.h index 5c1efed4..19fc34f1 100644 --- a/utilities/parser.h +++ b/utilities/parser.h @@ -48,7 +48,7 @@ class cParser //: public std::stringstream Output_ getToken( bool const ToLower = true, char const *Break = "\n\r\t ;" ) { getTokens( 1, ToLower, Break ); - Output_ output; + Output_ output{}; *this >> output; return output; }; inline diff --git a/utilities/utilities.cpp b/utilities/utilities.cpp index efaf3e6c..804ca66c 100644 --- a/utilities/utilities.cpp +++ b/utilities/utilities.cpp @@ -339,6 +339,87 @@ std::string win1250_to_utf8(const std::string &Input) return output; } +namespace +{ +// unicode code points of the upper half of the windows-1250 code page; 0 marks the unassigned bytes +std::array 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 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(Text[idx])}; + auto const length{utf8_sequence_length(lead)}; + if (length == 0 || idx + length > size) + { + return false; + } + auto codepoint{std::to_integer(lead & (std::byte{0xFF} >> (length > 1 ? length + 1 : 1)))}; + for (std::size_t offset = 1; offset < length; ++offset) + { + auto const next{static_cast(Text[idx + offset])}; + if ((next & std::byte{0xC0}) != std::byte{0x80}) + { + return false; + } + codepoint = (codepoint << 6) | std::to_integer(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(static_cast(character))}; + Output += (value < 0x80 ? static_cast(value) : win1250_upperhalf[value - 0x80]); + } +} + // Ra: tymczasowe rozwiązanie kwestii zagranicznych (czeskich) napisów char charsetconversiontable[] = "E?,?\"_++?%Sstzz" " ^^L$A|S^CS<--RZo±,l'uP.,as>L\"lz" diff --git a/utilities/utilities.h b/utilities/utilities.h index 6e761380..6b8111e3 100644 --- a/utilities/utilities.h +++ b/utilities/utilities.h @@ -166,6 +166,10 @@ void win1250_to_ascii(std::string &Input); std::string Bezogonkow(std::string Input, bool const Underscorestospaces = false); std::string win1250_to_utf8(const std::string &input); +// decodes utf-8 encoded text into unicode code points. returns false if the text isn't valid utf-8 +bool utf8_to_utf32(std::string const &Text, std::u32string &Output); +// decodes windows-1250 (ansi) encoded text into unicode code points +void win1250_to_utf32(std::string const &Text, std::u32string &Output); inline std::string extract_value(std::string const &Key, std::string const &Input) { diff --git a/vehicle/Driver.cpp b/vehicle/Driver.cpp index b6429a18..92d03d57 100644 --- a/vehicle/Driver.cpp +++ b/vehicle/Driver.cpp @@ -2231,7 +2231,7 @@ void TController::AutoRewident() BrakingInitialLevel = IsHeavyCargoTrain ? 1.25 : IsCargoTrain ? 1.25 : 1.00; - BrakingLevelIncrease = IsHeavyCargoTrain ? 0.25 : IsCargoTrain ? 0.25 : 0.25; + BrakingLevelIncrease = 0.25; if( is_emu() ) { auto ep_factor { ( BrakeSystem == TBrakeSystem::ElectroPneumatic ? 8 : 4 ) }; @@ -2320,7 +2320,8 @@ bool TController::CheckVehicles(TOrders user) p = pVehicle->FirstFind(d); // pojazd na czele składu if (!p) p = pVehicle; - pVehicles[end::front] = p; + auto *const frontvehicle { p }; // stays the consist head for the rest of this check + pVehicles[end::front] = frontvehicle; // liczenie pojazdów w składzie i ustalenie parametrów auto dir = d = 1 - d; // a dalej będziemy zliczać od czoła do tyłu fLength = 0.0; // długość składu do badania wyjechania za ograniczenie @@ -2369,7 +2370,7 @@ bool TController::CheckVehicles(TOrders user) if (iDrivigFlags & movePrimary) { // jeśli jest aktywnie prowadzącym pojazd, może zrobić własny porządek auto pantmask = 1; - p = pVehicles[end::front]; + p = frontvehicle; // establish ownership and vehicle order while (p) { @@ -2384,7 +2385,7 @@ bool TController::CheckVehicles(TOrders user) p = p->Next(); // pojazd podłączony od tyłu (licząc od czoła) } // with the order established the virtual train manager can do their work - p = pVehicles[ end::front ]; + p = frontvehicle; ControlledEnginesCount = p->MoverParameters->Power > 1.0 ? 1 : 0; auto hasheaters { false }; while (p) @@ -2457,7 +2458,7 @@ bool TController::CheckVehicles(TOrders user) } // detect push-pull train configurations and mark them accordingly - if( pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::control ) ) { + if( frontvehicle->is_connected( pVehicles[ end::rear ], coupling::control ) ) { // zmiana czoła przez zmianę kabiny iDrivigFlags |= movePushPull; } @@ -2482,7 +2483,7 @@ bool TController::CheckVehicles(TOrders user) } else { // jak dociska - pVehicles[end::front]->RaLightsSet(0, -1); + frontvehicle->RaLightsSet(0, -1); } } // enable door locks @@ -2493,7 +2494,7 @@ bool TController::CheckVehicles(TOrders user) // TODO: replace connection test with connection check between last engine and first car, specifically auto const isheatingcouplingactive { ( ControlledEnginesCount == 1 ? - pVehicles[ end::front ]->is_connected( pVehicles[ end::rear ], coupling::heating ) : + frontvehicle->is_connected( pVehicles[ end::rear ], coupling::heating ) : true ) }; auto const isheatingneeded { (is_emu() || is_dmu() ? true : @@ -4042,7 +4043,7 @@ void TController::SetTimeControllers() } } - if( std::abs( DizelPercentage_Speed - DizelActualPercentage ) > ( DizelPercentage > 1 ? 0 : 0 ) ) { + if( std::abs( DizelPercentage_Speed - DizelActualPercentage ) > 0 ) { if( PosDec > 0 && ( DizelActualPercentage - DizelPercentage_Speed > 50 @@ -4544,7 +4545,7 @@ bool TController::PutCommand( std::string NewCommand, double NewValue1, double N if (NewValue1 > 0.0 ? NewValue1 > fStopTime : false) fStopTime = NewValue1; // Ra: włączenie czekania bez zmiany komendy else - OrderList[OrderPos] = Wait_for_orders; // czekanie na komendę (albo dać OrderPos=0) + OrderCurrentSet( Wait_for_orders ); // czekanie na komendę (albo dać OrderPos=0) return true; } @@ -4986,13 +4987,13 @@ TController::PrepareDirection() { void TController::JumpToNextOrder( bool const Ignoremergedchangedirection ) { // wykonanie kolejnej komendy z tablicy rozkazów - if (OrderList[OrderPos] != Wait_for_orders) + if (auto const currentorder { OrderCurrentGet() }; currentorder != Wait_for_orders) { - if( (OrderList[OrderPos] & Change_direction) != 0 // jeśli zmiana kierunku - && OrderList[OrderPos] != Change_direction && false == Ignoremergedchangedirection ) { // ale nałożona na coś + if( (currentorder & Change_direction) != 0 // jeśli zmiana kierunku + && currentorder != Change_direction && false == Ignoremergedchangedirection ) { // ale nałożona na coś // usunięcie zmiany kierunku z innej komendy - OrderList[OrderPos] = TOrders(OrderList[OrderPos] & ~Change_direction); + OrderCurrentSet( TOrders(currentorder & ~Change_direction) ); OrderCheck(); return; } @@ -5032,37 +5033,43 @@ void TController::OrderCheck() // HACK: ensure consist doors will be closed on departure iDrivigFlags |= moveDoorOpened; } - if (OrderList[OrderPos] & Change_direction) // może być nałożona na inną i wtedy ma priorytet + auto const currentorder { OrderCurrentGet() }; + if (currentorder & Change_direction) // może być nałożona na inną i wtedy ma priorytet iDirectionOrder = -iDirection; // trzeba zmienić jawnie, bo się nie domyśli - else if (OrderList[OrderPos] == Obey_train) + else if (currentorder == Obey_train) iDrivigFlags |= moveStopPoint; // W4 są widziane - else if (OrderList[OrderPos] == Disconnect) + else if (currentorder == Disconnect) iVehicleCount = iVehicleCount < 0 ? 0 : iVehicleCount; // odczepianie lokomotywy - else if (OrderList[OrderPos] == Connect) + else if (currentorder == Connect) iDrivigFlags &= ~moveStopPoint; // podczas jazdy na połączenie nie zwracać uwagi na W4 - else if (OrderList[OrderPos] == Wait_for_orders) + else if (currentorder == Wait_for_orders) OrdersClear(); // czyszczenie rozkazów i przeskok do zerowej pozycji } void TController::OrderNext(TOrders NewOrder) { // ustawienie rozkazu do wykonania jako następny - if (OrderList[OrderPos] == NewOrder) + if (OrderCurrentGet() == NewOrder) return; // jeśli robi to, co trzeba, to koniec if (!OrderPos) OrderPos = 1; // na pozycji zerowej pozostaje czekanie OrderTop = OrderPos; // ale może jest czymś zajęty na razie if (NewOrder >= Shunt) // jeśli ma jechać { // ale może być zajęty chwilowymi operacjami - while (OrderList[OrderTop] != Wait_for_orders && OrderList[OrderTop] < Shunt) // jeśli coś robi + while (OrderTop >= 0 && OrderTop < maxorders - 1 + && OrderList[OrderTop] != Wait_for_orders && OrderList[OrderTop] < Shunt) // jeśli coś robi ++OrderTop; // pomijamy wszystkie tymczasowe prace } else { // jeśli ma ustawioną jazdę, to wyłączamy na rzecz operacji - while (OrderList[OrderTop] ? - OrderList[OrderTop] < Shunt && OrderList[OrderTop] != NewOrder : - false) // jeśli coś robi + while (OrderTop >= 0 && OrderTop < maxorders - 1 + && OrderList[OrderTop] != Wait_for_orders && OrderList[OrderTop] < Shunt && OrderList[OrderTop] != NewOrder) // jeśli coś robi ++OrderTop; // pomijamy wszystkie tymczasowe prace } + if (OrderTop < 0 || OrderTop >= maxorders) + { + ErrorLog("Commands overflow: order \"" + Order2Str(NewOrder) + "\" dropped"); + return; + } OrderList[OrderTop++] = NewOrder; // dodanie rozkazu jako następnego #if LOGORDERS OrdersDump( "OrderNext" ); // normalnie nie ma po co tego wypisywać @@ -5071,15 +5078,17 @@ void TController::OrderNext(TOrders NewOrder) void TController::OrderPush(TOrders NewOrder) { // zapisanie na stosie kolejnego rozkazu do wykonania - if (OrderPos == OrderTop && OrderList[OrderPos] < Shunt) // jeśli miałby być zapis na aktalnej pozycji + if (OrderPos == OrderTop && OrderCurrentGet() < Shunt) // jeśli miałby być zapis na aktalnej pozycji // ale nie jedzie ++OrderTop; // niektóre operacje muszą zostać najpierw dokończone => zapis na kolejnej + if (OrderTop < 0 || OrderTop >= maxorders) + { + ErrorLog("Commands overflow: order \"" + Order2Str(NewOrder) + "\" dropped"); + return; + } if (OrderList[OrderTop] != NewOrder) // jeśli jest to samo, to nie dodajemy OrderList[OrderTop++] = NewOrder; // dodanie rozkazu na stos - // if (OrderTop= maxorders) - ErrorLog("Commands overflow: The program will now crash"); #if LOGORDERS OrdersDump( "OrderPush: [" + Order2Str( NewOrder ) + "]" ); // normalnie nie ma po co tego wypisywać #endif @@ -5395,8 +5404,7 @@ TCommandType TController::BackwardScan( double const Range ) // najpierw sprawdzamy, czy semafor czy inny znak został przejechany auto const sl{e->input_location()}; // położenie komórki pamięci auto const pos{pVehicles[end::rear]->RearPosition()}; // pozycja tyłu - auto const sem{sl - pos}; // wektor do komórki pamięci od końca składu - if (dir.x * sem.x + dir.z * sem.z < 0) + if (auto const sem{sl - pos}; dir.x * sem.x + dir.z * sem.z < 0) // wektor do komórki pamięci od końca składu { // jeśli został minięty // iloczyn skalarny jest ujemny, gdy sygnał stoi z tyłu @@ -5406,13 +5414,10 @@ TCommandType TController::BackwardScan( double const Range ) return TCommandType::cm_Unknown; // nic } scanvel = e->input_value(1); // prędkość przy tym semaforze +#if LOGBACKSCAN // przeliczamy odległość od semafora - potrzebne by były współrzędne początku składu - scandist = glm::length(sem) - 2; // 2m luzu przy manewrach wystarczy - if (scandist < 0) - { - // ujemnych nie ma po co wysyłać - scandist = 0; - } + scandist = std::max(glm::length(sl - pos) - 2.0, 0.0); // 2m luzu przy manewrach wystarczy, ujemnych nie ma po co wysyłać +#endif } auto move{false}; // czy AI w trybie manewerowym ma dociągnąć pod S1 @@ -5425,32 +5430,23 @@ TCommandType TController::BackwardScan( double const Range ) } else { + // jeśli semafor jest daleko, a pojazd jedzie, to informujemy o zmianie prędkości + // jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył + // w przeciwnym razie ustawiamy prędkość tylko wtedy, gdy ma ruszyć, stanąć albo ma stać + // ruszać stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam +#if LOGBACKSCAN if (scandist > fMinProximityDist && mvOccupied->Vel > EU07_AI_NOMOVEMENT && (OrderCurrentGet() & (Shunt | Loose_shunt)) == 0) { - // jeśli semafor jest daleko, a pojazd jedzie, to informujemy o zmianie prędkości - // jeśli jedzie manewrowo, musi dostać SetVelocity, żeby sie na pociągowy przełączył -#if LOGBACKSCAN - // WriteLog(edir+"SetProximityVelocity "+AnsiString(scandist) + - // AnsiString(scanvel)); WriteLog(edir); -#endif - // SetProximityVelocity(scandist,scanvel,&sl); - return scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown; } else { - // ustawiamy prędkość tylko wtedy, gdy ma ruszyć, stanąć albo ma stać - // if ((MoverParameters->Vel==0.0)||(scanvel==0.0)) //jeśli stoi lub ma stanąć/stać - // semafor na tym torze albo lokomtywa stoi, a ma ruszyć, albo ma stanąć, albo nie - // ruszać stop trzeba powtarzać, bo inaczej zatrąbi i pojedzie sam - // PutCommand("SetVelocity",scanvel,e->Params[9].asMemCell->Value2(),&sl,stopSem); -#if LOGBACKSCAN WriteLog(edir + " - [SetVelocity] [" + to_string(scanvel, 2) + "] [" + to_string(e->input_value(2), 2) + "]"); -#endif - return scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown; } +#endif + return scanvel > 0 ? TCommandType::cm_SetVelocity : TCommandType::cm_Unknown; } } // reakcja AI w trybie manewrowym dodatkowo na sygnały manewrowe @@ -8118,7 +8114,7 @@ TController::check_route_behind( double const Range ) { } iDirectionOrder = -iDirection; // zmiana kierunku jazdy // zmiana kierunku bez psucia kolejnych komend - OrderList[ OrderPos ] = TOrders( OrderCurrentGet() | Change_direction ); + OrderCurrentSet( TOrders( OrderCurrentGet() | Change_direction ) ); } } } diff --git a/vehicle/Driver.h b/vehicle/Driver.h index b76dbf08..644b8122 100644 --- a/vehicle/Driver.h +++ b/vehicle/Driver.h @@ -482,6 +482,7 @@ private: void OrderPush( TOrders NewOrder ); void OrderNext( TOrders NewOrder ); inline TOrders OrderCurrentGet() const; + inline void OrderCurrentSet( TOrders const Order ); inline TOrders OrderNextGet() const; void OrderCheck(); void OrdersInit( double fVel ); @@ -542,7 +543,7 @@ private: double fLastVel = 0.0; // prędkość na poprzednio sprawdzonym torze TTrack *tLast = nullptr; // ostatni analizowany tor basic_event *eSignSkip = nullptr; // można pominąć ten SBL po zatrzymaniu - static constexpr std::size_t npos{ std::size_t( -1 ) }; + static constexpr std::size_t npos{ std::numeric_limits::max() }; std::size_t SemNextIndex{ npos }; std::size_t SemNextStopIndex{ npos }; double dMoveLen = 0.0; // odległość przejechana od ostatniego sprawdzenia tabelki @@ -645,9 +646,23 @@ private: }; inline TOrders TController::OrderCurrentGet() const { + if( OrderPos < 0 || OrderPos >= maxorders ) { + return Wait_for_orders; + } return OrderList[ OrderPos ]; } -inline TOrders TController::OrderNextGet() const { - return OrderList[ OrderPos + 1 ]; +inline void TController::OrderCurrentSet( TOrders const Order ) { + if( OrderPos < 0 || OrderPos >= maxorders ) { + return; + } + OrderList[ OrderPos ] = Order; +} + +inline TOrders TController::OrderNextGet() const { + if( OrderPos < 0 || OrderPos >= maxorders ) { + return Wait_for_orders; + } + // the order list wraps around, same as in JumpToNextOrder() + return OrderList[ ( OrderPos + 1 ) % maxorders ]; } diff --git a/vehicle/DynObj.cpp b/vehicle/DynObj.cpp index a6c57f56..707c1390 100644 --- a/vehicle/DynObj.cpp +++ b/vehicle/DynObj.cpp @@ -1408,7 +1408,7 @@ TDynamicObject * TDynamicObject::ABuScanNearestObject(glm::vec3 pos, TTrack *Tra TDynamicObject *FoundedObj; FoundedObj = ABuFindNearestObject(pos, Track, this, CouplNr); // zwraca numer sprzęgu znalezionego pojazdu - if (FoundedObj == nullptr) + if (FoundedObj == nullptr && Track != nullptr) { double ActDist; // Przeskanowana odleglosc. double CurrDist = 0; // Aktualna dlugosc toru. @@ -1448,7 +1448,7 @@ TDynamicObject * TDynamicObject::ABuScanNearestObject(glm::vec3 pos, TTrack *Tra ActDist = ScanDist; } else // Jesli nie ma, to wychodzimy. - ActDist = ScanDist; + break; } } // Koniec szukania najblizszego toru z jakims obiektem. return FoundedObj; diff --git a/vehicle/Gauge.cpp b/vehicle/Gauge.cpp index a510b05f..63c042d3 100644 --- a/vehicle/Gauge.cpp +++ b/vehicle/Gauge.cpp @@ -241,8 +241,7 @@ TGauge::Load_mapping( cParser &Input, TGauge::scratch_data &Scratchpad ) { gaugetype == "return" ? TGaugeType::push : gaugetype == "delayed" ? TGaugeType::push_delayed : gaugetype == "pushtoggle" ? TGaugeType::pushtoggle : - gaugetype == "toggle" ? TGaugeType::toggle : - TGaugeType::toggle; // default + TGaugeType::toggle; // "toggle" and default } else if( key == "soundinc:" ) { m_soundfxincrease.deserialize( Input, sound_type::single ); diff --git a/vehicle/Train.cpp b/vehicle/Train.cpp index 8d2117c4..a77c5749 100644 --- a/vehicle/Train.cpp +++ b/vehicle/Train.cpp @@ -31,6 +31,7 @@ http://mozilla.org/MPL/2.0/. #include "application/application.h" #include "rendering/renderer.h" #include +#include #include #include /* @@ -646,9 +647,8 @@ bool TTrain::Init(TDynamicObject *NewDynamicObject, bool e3d) } DynamicSet(NewDynamicObject); - if (!e3d) - if (DynamicObject->Mechanik == nullptr) - return false; + if (!e3d && DynamicObject->Mechanik == nullptr) + return false; DynamicObject->MechInside = true; @@ -1435,7 +1435,7 @@ void TTrain::OnCommand_secondcontrollerincrease(TTrain *Train, command_data cons // HACK: potentially animate push or pushtoggle control if (Train->ggScndCtrl.is_push()) { - auto const activeposition{Train->ggScndCtrl.is_toggle() ? 1.f : 1.f}; + auto const activeposition{1.f}; auto const neutralposition{Train->ggScndCtrl.is_toggle() ? 0.5f : 0.f}; Train->ggScndCtrl.UpdateValue(Command.action == GLFW_RELEASE ? neutralposition : activeposition, Train->dsbSwitch); } @@ -12398,8 +12398,18 @@ uint16_t TTrain::id() return vid; } -#include -#include +namespace +{ +// updates trains in range [Start, End) +void update_trains(std::deque const &Trains, std::size_t const Start, std::size_t const End, double const Dt) +{ + for (std::size_t j = Start; j < End; ++j) + { + if (Trains[j]) + Trains[j]->Update(Dt); + } +} +} // namespace void train_table::updateAsync(double dt) { @@ -12407,8 +12417,10 @@ void train_table::updateAsync(double dt) const size_t total = m_items.size(); const size_t chunkSize = (total + threads - 1) / threads; - std::vector> futures; - futures.reserve(threads); + std::vector workers; + std::vector> results; // przekazują wyjątki z wątków do głównego wątku + workers.reserve(threads); + results.reserve(threads); for (int i = 0; i < threads; ++i) { @@ -12418,21 +12430,17 @@ void train_table::updateAsync(double dt) if (start >= end) break; // brak więcej danych - futures.emplace_back(std::async(std::launch::async, - [this, start, end, dt]() - { - for (size_t j = start; j < end; ++j) - { - TTrain *train = m_items[j]; - if (train) - train->Update(dt); - } - })); + std::packaged_task task([this, start, end, dt]() { update_trains(m_items, start, end, dt); }); + results.emplace_back(task.get_future()); + workers.emplace_back(std::move(task)); } // Poczekaj aż wszystkie wątki skończą - for (auto &f : futures) - f.get(); + for (auto &worker : workers) + worker.join(); + + for (auto &result : results) + result.get(); // Teraz kasowanie (tylko w głównym wątku) for (TTrain *train : m_items) diff --git a/world/Segment.cpp b/world/Segment.cpp index 2afa264e..c963922a 100644 --- a/world/Segment.cpp +++ b/world/Segment.cpp @@ -294,7 +294,7 @@ TSegment::find_nearest_point( glm::dvec3 const &Point ) const { else { // for curves iterate through segment chunks, and find the one which gives us the least distance to the specified point double distance = std::numeric_limits::max(); - double nearest; + double nearest { 0.0 }; // NOTE: we're reusing already created segment chunks, which are created based on splinefidelity setting // this means depending on splinefidelity the results can be potentially slightly different for( int segmentidx = 0; segmentidx < iSegCount; ++segmentidx ) {