17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-17 17:59:19 +02:00
Files
maszyna/application/driveruipanels.cpp
Hirek193 eed44dc858 hardware protocol v2.1: device log, diagnostic routines, manual links
Three additions, all driven from the simulator debug panel.

DEVICE_LOG lets a controller send text lines; they reach the simulator log
and a per connection view in the panel which keeps the last 64 of them.

Diagnostic routines: a controller lists the procedures it offers, the
operator picks one from a drop down and starts it, and the controller
reports progress and the result. While a routine runs the controller can
ask the simulator to show a message box with its own text and buttons and
gets back which one was pressed, which is what a valve or gauge
calibration needs. The message box appears whether the debug panel is
open or not.

Links can now be disconnected and reconnected by hand: disconnecting
closes the serial port so the controller can be reprogrammed, and the
session is rebuilt from scratch on reconnect. Ports which appear while
the simulation runs can be picked from a refreshed list, given a baud
rate and added as a new controller, so the manager now starts even with
no uart2 entries in eu07.ini.
2026-09-06 11:29:26 +00:00

1934 lines
79 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/.
*/
#include "stdafx.h"
#include "application/driveruipanels.h"
#include "utilities/Globals.h"
#include "application/application.h"
#include "utilities/translation.h"
#include "simulation/simulation.h"
#include "simulation/simulationtime.h"
#include "simulation/simulationenvironment.h"
#include "utilities/Timer.h"
#include "world/Event.h"
#include "world/TractionPower.h"
#include "vehicle/Camera.h"
#include "world/mtable.h"
#include "vehicle/Train.h"
#include "vehicle/Driver.h"
#include "model/AnimModel.h"
#include "vehicle/DynObj.h"
#include "model/Model3d.h"
#include "rendering/renderer.h"
#include "utilities/Logs.h"
#include "widgets/vehicleparams.h"
#include "utilities/U8.h"
#define DRIVER_HINT_CONTENT
#include "application/driverhints.h"
#ifdef WITH_UART
#include "utilities/uart.h"
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
#include "hardware/hardware_manager.h"
#endif
void
drivingaid_panel::update() {
if( false == is_open ) { return; }
text_lines.clear();
auto const *train { simulation::Train };
auto const *controlled { ( train ? train->Dynamic() : nullptr ) };
if( controlled == nullptr
|| controlled->Mechanik == nullptr ) { return; }
auto const *mover = controlled->MoverParameters;
auto const *driver = controlled->Mechanik;
auto const *owner = controlled->ctOwner != nullptr ? controlled->ctOwner : controlled->Mechanik;
{ // throttle, velocity, speed limits and grade
std::string expandedtext;
if( is_expanded ) {
// grade
std::string gradetext;
auto const reverser { ( mover->DirActive > 0 ? 1 : -1 ) };
auto const grade { controlled->VectorFront().y * 100 * ( controlled->DirectionGet() == reverser ? 1 : -1 ) * reverser };
if( std::abs( grade ) >= 0.25 ) {
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C(" Grade: %.1f%%%%"),
grade );
gradetext = m_buffer.data();
}
// next speed limit
auto const speedlimit { static_cast<int>( owner->VelDesired ) };
auto nextspeedlimit { speedlimit };
auto nextspeedlimitdistance { std::numeric_limits<double>::max() };
if( speedlimit != 0 ) { // if we aren't allowed to move then any next speed limit is irrelevant
// nie przekraczać rozkladowej
auto const schedulespeedlimit { (
(owner->OrderCurrentGet() & (Obey_train | Bank)) != 0 && owner->TrainParams.TTVmax > 0.0 ? static_cast<int>( owner->TrainParams.TTVmax ) :
(owner->OrderCurrentGet() & (Obey_train | Bank)) == 0 ? static_cast<int>( owner->fShuntVelocity ) :
-1 ) };
// first take note of any speed change which should occur after passing potential current speed limit
if( owner->VelLimitLastDist.second > 0 ) {
nextspeedlimit = min_speed( schedulespeedlimit, static_cast<int>( owner->VelLimitLastDist.first ) );
nextspeedlimitdistance = owner->VelLimitLastDist.second;
}
// then take into account speed change ahead, compare it with speed after potentially clearing last limit
// lower of these two takes priority; otherwise limit lasts at least until potential last limit is cleared
auto const noactivespeedlimit { owner->VelLimitLastDist.second < 0 };
auto const speedatproximitydistance { min_speed( schedulespeedlimit, static_cast<int>( owner->VelNext ) ) };
if( speedatproximitydistance == nextspeedlimit ) {
if( noactivespeedlimit ) {
nextspeedlimit = speedatproximitydistance;
nextspeedlimitdistance = owner->ActualProximityDist;
}
}
else if( speedatproximitydistance < nextspeedlimit ) {
// if the speed limit ahead is more strict than our current limit, it's important enough to report
if( speedatproximitydistance < owner->VelDesired ) {
nextspeedlimit = speedatproximitydistance;
nextspeedlimitdistance = owner->ActualProximityDist;
}
// otherwise report it only if it's located after our current (lower) limit ends
else if( owner->ActualProximityDist > nextspeedlimitdistance ) {
nextspeedlimit = speedatproximitydistance;
nextspeedlimitdistance = owner->ActualProximityDist;
}
}
else if( noactivespeedlimit ) { // implicit proximity > last, report only if last limit isn't present
nextspeedlimit = speedatproximitydistance;
nextspeedlimitdistance = owner->ActualProximityDist;
}
// HACK: if our current speed limit extends beyond our scan range don't display potentially misleading information about its length
if( nextspeedlimitdistance >= EU07_AI_SPEEDLIMITEXTENDSBEYONDSCANRANGE ) {
nextspeedlimit = speedlimit;
}
// HACK: hide next speed limit if the 'limit' is a vehicle in front of us
else if( owner->ActualProximityDist == std::abs( owner->TrackObstacle() ) ) {
nextspeedlimit = speedlimit;
}
}
std::string nextspeedlimittext;
if( nextspeedlimit != speedlimit ) {
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C(", new limit: %d km/h in %.1f km"),
nextspeedlimit,
nextspeedlimitdistance * 0.001 );
nextspeedlimittext = m_buffer.data();
}
// current speed and limit
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C(" Speed: %d km/h (limit %d km/h%s)%s"),
static_cast<int>( std::floor( mover->Vel ) ),
speedlimit,
nextspeedlimittext.c_str(),
gradetext.c_str() );
expandedtext = m_buffer.data();
}
// base data and optional bits put together
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("Throttle: %3d+%d %c%s"),
mover->EIMCtrlType > 0 ? std::max(0, static_cast<int>(100.4 * mover->eimic_real)) : driver->Controlling()->MainCtrlPos,
mover->EIMCtrlType > 0 ? driver->Controlling()->MainCtrlPos : driver->Controlling()->ScndCtrlPos,
mover->SpeedCtrlUnit.IsActive ? 'T' :
mover->DirActive > 0 ? 'D' :
mover->DirActive < 0 ? 'R' :
'N',
expandedtext.c_str());
text_lines.emplace_back( m_buffer.data(), Global.UITextColor );
}
{ // brakes, air pressure
std::string expandedtext;
if( is_expanded ) {
std::snprintf (
m_buffer.data(), m_buffer.size(),
STR_C(" Pressure: %.2f kPa (train pipe: %.2f kPa)"),
mover->BrakePress * 100,
mover->PipePress * 100 );
expandedtext = m_buffer.data();
}
auto const basicbraking { mover->fBrakeCtrlPos };
auto const eimicbraking { std::max( 0.0, -100.0 * mover->eimic_real ) };
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("Brakes: %5.1f+%-2.0f%c%s"),
// ( mover->EIMCtrlType == 0 ? basicbraking : mover->EIMCtrlType == 3 ? ( mover->UniCtrlIntegratedBrakeCtrl ? eimicbraking : basicbraking ) : eimicbraking ),
mover->UniCtrlIntegratedBrakeCtrl ? eimicbraking : basicbraking,
mover->LocalBrakePosA * LocalBrakePosNo,
mover->SlippingWheels ? '!' : ' ',
expandedtext.c_str() );
text_lines.emplace_back( m_buffer.data(), Global.UITextColor );
}
{ // alerter, hints
std::string expandedtext;
if( is_expanded ) {
auto const stoptime { static_cast<int>( owner->ExchangeTime ) };
if( stoptime > 0 ) {
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C(" Loading/unloading in progress (%d s left)"),
stoptime );
expandedtext = m_buffer.data();
}
else {
auto const trackobstacledistance { std::abs( owner->TrackObstacle() ) };
if( trackobstacledistance <= 75.0 ) {
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C(" Another vehicle ahead (distance: %.1f m)"),
trackobstacledistance );
expandedtext = m_buffer.data();
}
}
}
std::string textline =
mover->SecuritySystem.is_vigilance_blinking() && (train != nullptr ? train->fBlinkTimer > 0 : true) ? STR("!ALERTER! ") : " ";
textline +=
mover->SecuritySystem.is_cabsignal_blinking() ? STR("!SHP!") : " ";
text_lines.emplace_back( textline + " " + expandedtext, Global.UITextColor );
}
}
void
scenario_panel::update() {
if( false == is_open ) { return; }
text_lines.clear();
auto const *train { simulation::Train };
auto const *controlled { ( train ? train->Dynamic() : nullptr ) };
auto const &camera { Global.pCamera };
m_nearest = false == FreeFlyModeFlag ? controlled :
camera.m_owner != nullptr ? camera.m_owner :
std::get<TDynamicObject *>(simulation::Region->find_vehicle(camera.Pos, 20, false, false)); // w trybie latania lokalizujemy wg mapy
if( m_nearest == nullptr ) { return; }
auto const *owner { (
m_nearest->Mechanik != nullptr && m_nearest->Mechanik->primary() ?
m_nearest->Mechanik :
m_nearest->ctOwner ) };
if( owner == nullptr ) { return; }
std::string textline =
STR("Current task:") + "\n "
+ owner->OrderCurrent();
text_lines.emplace_back( textline, Global.UITextColor );
}
void
scenario_panel::render() {
if( false == is_open ) { return; }
if( true == text_lines.empty() ) { return; }
if( m_nearest == nullptr ) { return; } // possibly superfluous given the above but, eh
auto flags =
ImGuiWindowFlags_NoFocusOnAppearing
| ImGuiWindowFlags_NoCollapse
| ( size.x > 0 ? ImGuiWindowFlags_NoResize : 0 );
if( size.x > 0 ) {
ImGui::SetNextWindowSize( ImVec2S( size.x, size.y ) );
}
if( size_min.x > 0 ) {
ImGui::SetNextWindowSizeConstraints( ImVec2S( size_min.x, size_min.y ), ImVec2S( size_max.x, size_max.y ) );
}
auto const panelname { (
title.empty() ?
m_name :
title )
+ "###" + m_name };
if( true == ImGui::Begin( panelname.c_str(), &is_open, flags ) ) {
// potential assignment section
auto const *owner { (
m_nearest->Mechanik != nullptr && m_nearest->Mechanik->primary() ?
m_nearest->Mechanik :
m_nearest->ctOwner ) };
if( owner != nullptr ) {
auto const assignmentheader { STR("Assignment") };
if( false == owner->assignment().empty()
&& true == ImGui::CollapsingHeader(assignmentheader.c_str()) ) {
ImGui::TextWrapped( "%s", owner->assignment().c_str() );
ImGui::Separator();
}
}
// current task
for( auto const &line : text_lines ) {
ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() );
}
// hints
if( owner != nullptr ) {
if( true == ImGui::CollapsingHeader( STR_C("Hints"), ImGuiTreeNodeFlags_DefaultOpen ) ) {
for( auto const &hint : owner->m_hints ) {
auto const isdone { std::get<TController::hintpredicate>( hint )( std::get<float>( hint ) ) };
auto const hintcolor{ (
isdone ?
colors::uitextgreen :
Global.UITextColor ) };
ImGui::PushStyleColor( ImGuiCol_Text, { hintcolor.r, hintcolor.g, hintcolor.b, hintcolor.a } );
ImGui::TextWrapped( Translations.lookup_c(driver_hints_texts[(size_t)std::get<driver_hint>( hint )], true), std::get<float>( hint ) );
ImGui::PopStyleColor();
}
}
}
}
ImGui::End();
}
void
timetable_panel::update() {
if( false == is_open ) { return; }
text_lines.clear();
m_tablelines.clear();
auto const *train { simulation::Train };
auto const *controlled { ( train ? train->Dynamic() : nullptr ) };
auto const &camera { Global.pCamera };
auto const &time { simulation::Time.data() };
{ // current time
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("%-*.*s Time: %d:%02d:%02d"),
37, 37,
STR_C("Timetable"),
time.wHour,
time.wMinute,
time.wSecond );
title = m_buffer.data();
}
auto *vehicle { (
false == FreeFlyModeFlag ? controlled :
camera.m_owner != nullptr ? camera.m_owner :
std::get<TDynamicObject *>( simulation::Region->find_vehicle( camera.Pos, 20, false, false ) ) ) }; // w trybie latania lokalizujemy wg mapy
if( vehicle == nullptr ) { return; }
// if the nearest located vehicle doesn't have a direct driver, try to query its owner
auto const *owner = vehicle->Mechanik != nullptr && vehicle->Mechanik->primary() ? vehicle->Mechanik : vehicle->ctOwner;
if( owner == nullptr ) { return; }
auto const &table = owner->TrainTimetable();
// destination
{
auto textline = Bezogonkow( owner->Relation(), true );
if( false == textline.empty() ) {
textline += " (" + Bezogonkow( owner->TrainName(), true ) + ")";
}
text_lines.emplace_back( textline, Global.UITextColor );
}
if( false == is_expanded ) {
// next station
auto const nextstation = owner->NextStop();
if( false == nextstation.empty() ) {
// jeśli jest podana relacja, to dodajemy punkt następnego zatrzymania
auto textline = " -> " + nextstation;
text_lines.emplace_back( textline, Global.UITextColor );
}
}
if( is_expanded ) {
if( owner->is_train() ) {
// consist data
auto consistmass { owner->fMass };
auto consistlength { owner->fLength };
if( false == owner->is_dmu()
&& false == owner->is_emu() ) {
//odejmij lokomotywy czynne, a przynajmniej aktualną
consistmass -= owner->pVehicle->MoverParameters->TotalMass;
// subtract potential other half of a two-part vehicle
auto const *previous { owner->pVehicle->Prev( coupling::permanent ) };
if( previous != nullptr ) { consistmass -= previous->MoverParameters->TotalMass; }
auto const *next { owner->pVehicle->Next( coupling::permanent ) };
if( next != nullptr ) { consistmass -= next->MoverParameters->TotalMass; }
}
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("Consist weight: %d t (specified) %d t (actual)\nConsist length: %d m"),
static_cast<int>( table.LocLoad ),
static_cast<int>( consistmass / 1000 ),
static_cast<int>( consistlength ) );
text_lines.emplace_back( m_buffer.data(), Global.UITextColor );
text_lines.emplace_back( "", Global.UITextColor );
}
if( 0 == table.StationCount ) {
// only bother if there's stations to list
text_lines.emplace_back( STR("(no timetable)"), Global.UITextColor );
}
else {
// header
m_tablelines.emplace_back(U8("┌─────┬────────────────────────────────────┬─────────┬─────┐"), Global.UITextColor);
TMTableLine const *tableline;
for( int i = table.StationStart; i <= table.StationCount; ++i ) {
// wyświetlenie pozycji z rozkładu
tableline = table.TimeTable + i; // linijka rozkładu
bool vmaxchange { true };
if( i > table.StationStart ) {
auto const *previoustableline { tableline - 1 };
if( tableline->vmax == previoustableline->vmax ) {
vmaxchange = false;
}
}
std::string vmax { " " };
if( true == vmaxchange ) {
vmax += to_string( tableline->vmax, 0 );
vmax = vmax.substr( vmax.size() - 3, 3 ); // z wyrównaniem do prawej
}
auto const station { (
Bezogonkow( tableline->StationName, true )
+ " " )
.substr( 0, 34 ) };
auto const location { (
( tableline->km > 0.0 ?
to_string( tableline->km, 2 ) :
"" )
+ " " )
.substr( 0, 34 - tableline->StationWare.size() ) };
auto const arrival { (
tableline->Ah >= 0 ?
std::to_string( int( 100 + tableline->Ah ) ).substr( 1, 2 ) + ":" + to_minutes_str( tableline->Am, true, 3 ) :
U8(" │ ") ) };
auto const departure { (
tableline->Dh >= 0 ?
std::to_string( int( 100 + tableline->Dh ) ).substr( 1, 2 ) + ":" + to_minutes_str( tableline->Dm, true, 3 ) :
U8(" │ ") ) };
auto const candepart { (
table.StationStart < table.StationIndex
&& i < table.StationIndex
&& ( tableline->Ah < 0 // pass-through, always valid
|| tableline->is_maintenance // maintenance stop, always valid
|| time.wHour * 60 + time.wMinute + time.wSecond * 0.0167 >= tableline->Dh * 60 + tableline->Dm ) ) };
auto const loadchangeinprogress { ( static_cast<int>(std::ceil(-1.0 * owner->fStopTime)) > 0 ) };
auto const isatpassengerstop { true == owner->IsAtPassengerStop && vehicle->MoverParameters->Vel < 1.0 };
auto const traveltime { (
i < 2 ? " " :
tableline->Ah >= 0 ? to_minutes_str( CompareTime( table.TimeTable[ i - 1 ].Dh, table.TimeTable[ i - 1 ].Dm, tableline->Ah, tableline->Am ), false, 3 ) :
to_minutes_str( std::max( 0.0, CompareTime( table.TimeTable[ i - 1 ].Dh, table.TimeTable[ i - 1 ].Dm, tableline->Dh, tableline->Dm ) - 0.5 ), false, 3 ) ) };
auto const linecolor { (
i != table.StationStart ? Global.UITextColor :
loadchangeinprogress ? colors::uitextred :
candepart ? colors::uitextgreen : // czas minął i odjazd był, to nazwa stacji będzie na zielono
isatpassengerstop ? colors::uitextorange :
Global.UITextColor ) };
std::string const trackcount{ ( tableline->TrackNo == 1 ? U8(" ┃ ") : U8(" ║ " )) };
m_tablelines.emplace_back(U8("│ ") + vmax + U8(" │ ") + station + trackcount + arrival + U8(" │ ") + traveltime + U8(" │"),
linecolor );
m_tablelines.emplace_back(U8("│ │ ") + location + tableline->StationWare + trackcount + departure + U8(" │ │"),
linecolor );
// divider/footer
if( i < table.StationCount ) {
auto const *nexttableline { tableline + 1 };
std::string const vmaxnext{ ( tableline->vmax == nexttableline->vmax ? U8("│ ├") : U8("├─────┼") ) };
auto const trackcountnext{ ( nexttableline->TrackNo == 1 ? U8("╂") : U8("╫") ) };
m_tablelines.emplace_back(
vmaxnext + U8("────────────────────────────────────") + trackcountnext + U8("─────────┼─────┤"),
Global.UITextColor );
}
else {
m_tablelines.emplace_back(
U8("└─────┴────────────────────────────────────┴─────────┴─────┘"),
Global.UITextColor );
}
}
}
} // is_expanded
}
void
timetable_panel::render() {
if( false == is_open ) { return; }
if( true == text_lines.empty() ) { return; }
ImGui::PushFont(ui_layer::font_mono);
auto flags =
ImGuiWindowFlags_NoFocusOnAppearing
| ImGuiWindowFlags_NoCollapse
| ( size.x > 0 ? ImGuiWindowFlags_NoResize : 0 );
// HACK: Make sure the timetable window is of correct width when using a larger font size.
float horizontalScale = Global.ui_fontsize / 13;
if( size.x > 0 ) {
ImGui::SetNextWindowSize( ImVec2S( size.x * horizontalScale, size.y ) );
}
if( size_min.x > 0 ) {
ImGui::SetNextWindowSizeConstraints( ImVec2S( size_min.x * horizontalScale, size_min.y ), ImVec2S( size_max.x * horizontalScale, size_max.y ) );
}
auto const panelname { (
title.empty() ?
m_name :
title )
+ "###" + m_name };
if( true == ImGui::Begin( panelname.c_str(), &is_open, flags ) ) {
for( auto const &line : text_lines ) {
ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() );
}
if( is_expanded ) {
ImGui::PushStyleVar( ImGuiStyleVar_ItemSpacing, ImVec2( 1, 0 ) );
for( auto const &line : m_tablelines ) {
ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() );
}
ImGui::PopStyleVar();
}
}
ImGui::End();
ImGui::PopFont();
}
void debug_panel::update()
{
if( false == is_open ) { return; }
// input item bindings
m_input.train = simulation::Train;
m_input.controlled = m_input.train ? m_input.train->Dynamic() : nullptr;
m_input.camera = &Global.pCamera;
m_input.vehicle = false == FreeFlyModeFlag ? m_input.controlled :
m_input.camera->m_owner != nullptr ? m_input.camera->m_owner :
std::get<TDynamicObject *>(simulation::Region->find_vehicle(m_input.camera->Pos, 20, false, false)); // w trybie latania lokalizujemy wg mapy
m_input.mover =
m_input.vehicle != nullptr ? m_input.vehicle->MoverParameters : nullptr;
m_input.mechanik = m_input.vehicle != nullptr ? m_input.vehicle->Mechanik : nullptr;
// header section
text_lines.clear();
auto textline = "Version " + Global.asVersion;
text_lines.emplace_back( textline, Global.UITextColor );
// sub-sections
m_vehiclelines.clear();
m_enginelines.clear();
m_ailines.clear();
m_scantablelines.clear();
m_scenariolines.clear();
m_eventqueuelines.clear();
m_powergridlines.clear();
m_cameralines.clear();
m_rendererlines.clear();
#ifdef WITH_UART
m_uartlines.clear();
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
m_hardwarelines.clear();
#endif
update_section_vehicle( m_vehiclelines );
update_section_engine( m_enginelines );
update_section_ai( m_ailines );
update_section_scantable( m_scantablelines );
update_section_scenario( m_scenariolines );
update_section_eventqueue( m_eventqueuelines );
update_section_powergrid( m_powergridlines );
update_section_camera( m_cameralines );
update_section_renderer( m_rendererlines );
#ifdef WITH_UART
update_section_uart(m_uartlines);
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
update_section_hardware(m_hardwarelines);
#endif
}
void
debug_panel::render() {
#ifdef WITH_HARDWARE_PROTOCOL_V2
// a controller waiting for an answer must be visible whether the panel is open or not
render_hardware_prompt();
#endif
if( false == is_open ) { return; }
ImGui::PushFont(ui_layer::font_mono);
auto flags =
ImGuiWindowFlags_NoFocusOnAppearing
| ImGuiWindowFlags_NoCollapse
| ( size.x > 0 ? ImGuiWindowFlags_NoResize : 0 );
if( size.x > 0 ) {
ImGui::SetNextWindowSize( ImVec2S( size.x, size.y ) );
}
if( size_min.x > 0 ) {
ImGui::SetNextWindowSizeConstraints( ImVec2S( size_min.x, size_min.y ), ImVec2S( size_max.x, size_max.y ) );
}
auto const panelname { (
title.empty() ?
m_name :
title )
+ "###" + m_name };
if( true == ImGui::Begin( panelname.c_str(), &is_open, flags ) ) {
// header section
for( auto const &line : text_lines ) {
ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() );
}
// sections
ImGui::Separator();
if( true == render_section( "Vehicle", m_vehiclelines ) ) {
if( DebugModeFlag && m_input.mover && m_input.mover->DamageFlag != 0 ) {
if( true == ImGui::Button( "Stop and repair consist" ) ) {
command_relay relay;
relay.post(user_command::resetconsist, 0.0, 0.0, GLFW_PRESS, 0, glm::vec3(0.0f), &m_input.vehicle->name());
}
}
}
render_section( "Vehicle Engine", m_enginelines );
render_section( "Vehicle AI", m_ailines );
render_section( "Vehicle Scan Table", m_scantablelines );
render_section_scenario();
render_section_eventqueue();
if( true == render_section( "Power Grid", m_powergridlines ) ) {
// traction state debug
ImGui::Checkbox( "Debug Traction", &DebugTractionFlag );
}
render_section( "Camera", m_cameralines );
render_section( "Gfx Renderer / Statistics", m_rendererlines );
render_section_settings();
render_section_developer(); // Developer tools
#ifdef WITH_UART
if(true == render_section( "UART", m_uartlines)) {
int ports_num = UartStatus.available_ports.size();
char **avlports = new char*[ports_num];
for (int i=0; i < ports_num; i++) {
avlports[i] = (char *) UartStatus.available_ports[i].c_str();
}
ImGui::Combo("Port", &UartStatus.selected_port_index, avlports, ports_num);
ImGui::Combo("Baud", &UartStatus.selected_baud_index, uart_baudrates_list, uart_baudrates_list_num);
ImGui::Checkbox("Enabled", &UartStatus.enabled);
}
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
if( true == ImGui::CollapsingHeader( "Hardware Protocol v2" ) ) {
render_section_hardware();
}
#endif
// toggles
ImGui::Separator();
bool flag = DebugModeFlag;
if (ImGui::Checkbox("Debug Mode", &flag)) {
command_relay relay;
relay.post(user_command::debugtoggle, 0.0, 0.0, GLFW_RELEASE, 0);
}
}
ImGui::End();
ImGui::PopFont();
}
bool
debug_panel::render_section_scenario() {
if( false == render_section( "Scenario", m_scenariolines ) ) { return false; }
if( Application.is_client() ) {
// NOTE: simulation clients can't adjust scenarion state, this is reserved for the server/standalone instance
return true;
}
// fog slider
{
auto fogrange = std::log( Global.fFogEnd );
if( ImGui::SliderFloat(
( to_string( std::exp( fogrange ), 0, 5 ) + " m###fogend" ).c_str(), &fogrange, std::log( 10.0f ), std::log( 50000.0f ), "Fog distance" ) ) {
command_relay relay;
relay.post(
user_command::setweather,
std::clamp( std::exp( fogrange ), 10.0f, 50000.0f ),
Global.Overcast,
GLFW_PRESS, 0 );
}
}
{
auto Airtemperature = Global.AirTemperature;
if (ImGui::SliderFloat(
(to_string(Airtemperature, 1) + " deg C###Airtemperature").c_str(),
&Airtemperature, -35.0f, 40.0f, "Air Temperature"))
{
command_relay relay;
relay.post(
user_command::settemperature,
std::clamp(Airtemperature, -35.0f, 40.0f),
Global.Overcast,
GLFW_PRESS, 0 );
}
}
// cloud cover slider
{
if( ImGui::SliderFloat(
( to_string( Global.Overcast, 2, 5 ) + " (" + Global.Weather + ")###overcast" ).c_str(), &Global.Overcast, 0.0f, 2.0f, "Cloud cover" ) ) {
command_relay relay;
relay.post(
user_command::setweather,
Global.fFogEnd,
std::clamp( Global.Overcast, 0.0f, 2.0f ),
GLFW_PRESS, 0 );
}
}
// day of year slider
{
if( ImGui::SliderFloat(
( to_string( Global.fMoveLight, 0, 5 ) + " (" + Global.Season + ")###movelight" ).c_str(), &Global.fMoveLight, 0.0f, 364.0f, "Day of year" ) ) {
command_relay relay;
relay.post(
user_command::setdatetime,
std::clamp( Global.fMoveLight, 0.0f, 365.0f ),
simulation::Time.data().wHour * 60 + simulation::Time.data().wMinute,
GLFW_PRESS, 0 );
}
}
// dynamic material update checkbox
ImGui::Checkbox( "Update Item Materials", &Global.UpdateMaterials );
if( DebugModeFlag ) {
if( ImGui::Checkbox( "Force Daylight", &Global.FakeLight ) ) {
simulation::Environment.on_daylight_change();
}
}
// advanced options, only visible in debug mode
if( DebugModeFlag ) {
// time of day slider
{
ImGui::PushStyleColor( ImGuiCol_Text, { Global.UITextColor.r, Global.UITextColor.g, Global.UITextColor.b, Global.UITextColor.a } );
ImGui::TextUnformatted( "CAUTION: time change will affect simulation state" );
ImGui::PopStyleColor();
auto time = simulation::Time.data().wHour * 60 + simulation::Time.data().wMinute;
auto const timestring {
std::string( std::to_string( int( 100 + simulation::Time.data().wHour ) ).substr( 1, 2 )
+ ":"
+ std::string( std::to_string( int( 100 + simulation::Time.data().wMinute ) ).substr( 1, 2 ) ) ) };
if( ImGui::SliderInt( ( timestring + " (" + Global.Period + ")###simulationtime" ).c_str(), &time, 0, 1439, "Time of day" ) ) {
command_relay relay;
relay.post(
user_command::setdatetime,
Global.fMoveLight,
std::clamp( time, 0, 1439 ),
GLFW_PRESS, 0 );
}
}
// time acceleration
{
auto timerate { ( Global.fTimeSpeed == 60 ? 4 : Global.fTimeSpeed == 20 ? 3 : Global.fTimeSpeed == 5 ? 2 : 1 ) };
if( ImGui::SliderInt( ( "x " + to_string( Global.fTimeSpeed, 0 ) + "###timeacceleration" ).c_str(), &timerate, 1, 4, "Time acceleration" ) ) {
Global.fTimeSpeed = timerate == 4 ? 60 : timerate == 3 ? 20 : timerate == 2 ? 5 : 1;
}
}
// base draw range slider
{
ImGui::TextUnformatted("CAUTION: drawing range change can affect FPS badly");
auto drawrange = std::log(Global.BaseDrawRange);
if (ImGui::SliderFloat(
(to_string(std::exp(drawrange), 0, 5) + " m###drawrange").c_str(), &drawrange, std::log(100.0f), std::log(50000.0f), "Base drawing range")) {
Global.BaseDrawRange = std::clamp(std::exp(drawrange), 100.0f, 50000.0f);
}
}
}
return true;
}
bool
debug_panel::render_section_eventqueue() {
if( false == ImGui::CollapsingHeader( "Scenario Event Queue" ) ) { return false; }
// event queue name filter
ImGui::PushItemWidth( -1 );
ImGui::InputTextWithHint( "", "Search event queue", m_eventsearch.data(), m_eventsearch.size() );
ImGui::PopItemWidth();
// event queue
render_section( m_eventqueuelines );
// event queue activator filter
ImGui::Checkbox( "By This Vehicle Only", &m_eventqueueactivevehicleonly );
return true;
}
void
debug_panel::update_section_vehicle( std::vector<text_line> &Output ) {
if( m_input.vehicle == nullptr ) { return; }
if( m_input.mover == nullptr ) { return; }
auto const &vehicle { *m_input.vehicle };
auto const &mover { *m_input.mover };
auto const isowned { /* ( vehicle.Mechanik == nullptr ) && */ vehicle.ctOwner != nullptr && vehicle.ctOwner->Vehicle() != m_input.vehicle };
auto const isdieselenginepowered { mover.EngineType == TEngineType::DieselElectric || mover.EngineType == TEngineType::DieselEngine };
auto const isdieselinshuntmode { mover.ShuntMode && mover.EngineType == TEngineType::DieselElectric };
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("Name: %s%s\nLoad: %.0f %s\nStatus: %s%s\nCouplers:\n front: %s\n rear: %s"),
mover.Name.c_str(),
std::string( isowned ? STR(", owned by: ") + vehicle.ctOwner->OwnerName() : "" ).c_str(),
mover.LoadAmount,
mover.LoadType.name.c_str(),
mover.EngineDescription( 0 ).c_str(),
// TODO: put wheel flat reporting in the enginedescription()
std::string( mover.WheelFlat > 0.01 ? " Flat: " + to_string( mover.WheelFlat, 1 ) + " mm" : "" ).c_str(),
update_vehicle_coupler( end::front ).c_str(),
update_vehicle_coupler( end::rear ).c_str() );
Output.emplace_back( std::string{ m_buffer.data() }, Global.UITextColor );
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("Devices: %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%s%s\nPower transfers: %.0f@%.0f%s[%.0f]%s%.0f@%.0f :: %.0f@%.0f%s[%.0f]%s%.0f@%.0f"),
// devices
mover.Battery ? 'B' : '.',
mover.PantsValve.is_active ? '+' : '.',
mover.Pantographs[end::rear].valve.is_active ? 'O' :
mover.Pantographs[end::rear].valve.is_enabled ? 'o' :
'.',
mover.Pantographs[end::front].valve.is_active ? 'P' :
mover.Pantographs[end::front].valve.is_enabled ? 'p' :
'.',
mover.PantPressLockActive ? '!' :
mover.PantPressSwitchActive ? '*' :
'.',
mover.WaterPump.is_active ? 'W' :
false == mover.WaterPump.breaker ? '-' :
mover.WaterPump.is_enabled ? 'w' :
'.',
mover.WaterHeater.is_damaged ? '!' :
mover.WaterHeater.is_active ? 'H' :
false == mover.WaterHeater.breaker ? '-' :
mover.WaterHeater.is_enabled ? 'h' :
'.',
mover.FuelPump.is_active ? 'F' :
mover.FuelPump.is_enabled ? 'f' :
'.',
mover.OilPump.is_active ? 'O' :
mover.OilPump.is_enabled ? 'o' :
'.',
mover.Mains ? 'M' : '.',
mover.FuseFlag ? '!' : '.',
mover.ConverterFlag ? 'X' :
false == mover.ConverterAllowLocal ? '-' :
mover.ConverterAllow ? 'x' :
'.',
mover.ConvOvldFlag ? '!' : '.',
mover.CompressorFlag ? 'C' :
false == mover.CompressorAllowLocal ? '-' :
mover.CompressorAllow || (mover.CompressorStart == start_t::automatic && mover.CompressorSpeed > 0.0) ? 'c' :
'.',
mover.CompressorGovernorLock ? '!' : '.',
mover.StLinSwitchOff ? '-' :
mover.ControlPressureSwitch ? '!' :
mover.StLinFlag ? '+' :
'.',
mover.Heating ? 'H' :
mover.HeatingAllow ? 'h' :
'.',
mover.Hamulec->Releaser() ? '^' : '.',
std::string( m_input.mechanik ? STR(" R") + ( mover.Radio ? std::to_string( m_input.mechanik->iRadioChannel ) : "-" ) : "" ).c_str(),
std::string( isdieselenginepowered ? STR(" oil pressure: ") + to_string( mover.OilPump.pressure, 2 ) : "" ).c_str(),
// power transfers
// 3000v
mover.Couplers[ end::front ].power_high.voltage,
mover.Couplers[ end::front ].power_high.current,
std::string( mover.Couplers[ end::front ].power_high.is_local ? ":" : ":=" ).c_str(),
mover.EngineVoltage,
std::string( mover.Couplers[ end::rear ].power_high.is_local ? ":" : "=:" ).c_str(),
mover.Couplers[ end::rear ].power_high.voltage,
mover.Couplers[ end::rear ].power_high.current,
// 110v
mover.Couplers[ end::front ].power_110v.voltage,
mover.Couplers[ end::front ].power_110v.current,
std::string( mover.Couplers[ end::front ].power_110v.is_local ? ":" : ":=" ).c_str(),
mover.PowerCircuits[ 1 ].first,
std::string( mover.Couplers[ end::rear ].power_110v.is_local ? ":" : "=:" ).c_str(),
mover.Couplers[ end::rear ].power_110v.voltage,
mover.Couplers[ end::rear ].power_110v.current );
Output.emplace_back( m_buffer.data(), Global.UITextColor );
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("Controllers:\n master: %d(%d), secondary: %s\nEngine output: %.1f, current: %.0f\nRevolutions:\n engine: %.0f, motors: %.0f\n engine fans: %.0f, motor fans: %.0f+%.0f, cooling fans: %.0f+%.0f"),
// controllers
mover.MainCtrlPos,
mover.MainCtrlActualPos,
std::string( isdieselinshuntmode ? to_string( mover.AnPos, 2 ) + STR(" (shunt mode)") : std::to_string( mover.ScndCtrlPos ) + "(" + std::to_string( mover.ScndCtrlActualPos ) + ")" ).c_str(),
// engine
mover.EnginePower,
std::abs( mover.TrainType == dt_EZT ? mover.ShowCurrent( 0 ) : mover.Im ),
// revolutions
std::abs( mover.enrot ) * 60,
std::abs( mover.nrot ) * mover.Transmision.Ratio * 60,
mover.RventRot * 60,
std::abs( mover.MotorBlowers[end::front].revolutions ),
std::abs( mover.MotorBlowers[end::rear].revolutions ),
mover.dizel_heat.rpmw,
mover.dizel_heat.rpmw2 );
std::string textline { m_buffer.data() };
if( isdieselenginepowered ) {
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("\nTemperatures:\n engine: %.2f, oil: %.2f, water: %.2f%c%.2f"),
mover.dizel_heat.Ts,
mover.dizel_heat.To,
mover.dizel_heat.temperatura1,
mover.WaterCircuitsLink ? '-' : '|',
mover.dizel_heat.temperatura2 );
textline += m_buffer.data();
}
Output.emplace_back( textline, Global.UITextColor );
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("Brakes:\n train: %.2f (mode: %d, delay: %s, load flag: %d)\n independent: %.2f (%.2f), manual: %.2f, spring: %.2f\nBrake cylinder pressures:\n train: %.2f, independent: %.2f, status: 0x%.2x\nPipe pressures:\n brake: %.2f (hat: %.2f), main: %.2f, control: %.2f\nTank pressures:\n auxiliary: %.2f, main: %.2f, control: %.2f"),
// brakes
mover.fBrakeCtrlPos,
mover.BrakeOpModeFlag,
update_vehicle_brake().c_str(),
mover.LoadFlag,
mover.LocalBrakePosA,
mover.LocalBrakePosAEIM,
mover.ManualBrakePos / static_cast<float>(ManualBrakePosNo),
mover.SpringBrake.Activate ? 1.f : 0.f,
// cylinders
mover.BrakePress,
mover.LocBrakePress,
mover.Hamulec->GetBrakeStatus(),
// pipes
mover.PipePress,
mover.BrakeCtrlPos2,
mover.ScndPipePress,
mover.CntrlPipePress,
// tanks
mover.Hamulec->GetBRP(),
mover.Compressor,
mover.Hamulec->GetCRP() );
textline = m_buffer.data();
if( mover.EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) {
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C(" pantograph: %.2f%cMT"),
mover.PantPress,
mover.bPantKurek3 ? '-' : '|' );
textline += m_buffer.data();
}
Output.emplace_back( textline, Global.UITextColor );
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("Forces:\n tractive: %.1f, brake: %.1f, drag: %.2f%s\nAcceleration:\n tangential: %.2f, normal: %.2f (path radius: %s)\nVelocity: %.2f, distance traveled: %.2f\nPosition: [%.2f, %.2f, %.2f]"),
// forces
mover.Ft * 0.001f * ( mover.CabOccupied ? mover.CabOccupied : vehicle.ctOwner ? vehicle.ctOwner->Controlling()->CabOccupied : 1 ) + 0.001f,
mover.Fb * 0.001f,
mover.FrictionForce() * 0.001f,
mover.SlippingWheels ? " (!)" : "",
// acceleration
mover.AccSVBased,
mover.AccN + 0.001f,
std::string( std::abs( mover.RunningShape.R ) > 15000.0 ? "~0" : to_string( mover.RunningShape.R, 0 ) ).c_str(),
// velocity
vehicle.GetVelocity(),
mover.DistCounter,
// position
vehicle.GetPosition().x,
vehicle.GetPosition().y,
vehicle.GetPosition().z );
Output.emplace_back( m_buffer.data(), Global.UITextColor );
if( mover.EnginePowerSource.SourceType == TPowerSource::CurrentCollector ) {
std::snprintf(m_buffer.data(), m_buffer.size(), STR_C("Power use:\n drawn: %.1f kWh\n returned: %.1f kWh\n balance: %.1f kWh"),
mover.EnergyMeter.first, -mover.EnergyMeter.second, mover.EnergyMeter.first + mover.EnergyMeter.second);
textline = m_buffer.data();
if( DebugTractionFlag ) {
for( int i = 0; i < vehicle.iAnimType[ ANIM_PANTS ]; ++i ) { // pętla po wszystkich pantografach
auto const *p { vehicle.pants[ i ].fParamPants };
if( p && p->hvPowerWire ) {
auto const *powerwire { p->hvPowerWire };
std::snprintf(
m_buffer.data(), m_buffer.size(),
STR_C("\n pantograph %d: drawing from: [%s, %s] through: [%.2f, %.2f, %.2f]"),
i,
powerwire->psPower[ 0 ] ? powerwire->psPower[ 0 ]->name().c_str() : "none",
powerwire->psPower[ 1 ] ? powerwire->psPower[ 1 ]->name().c_str() : "none",
powerwire->pPoint1[ 0 ],
powerwire->pPoint1[ 1 ],
powerwire->pPoint1[ 2 ] );
textline += m_buffer.data();
}
}
}
Output.emplace_back( textline, Global.UITextColor );
}
if (!std::isnan(last_time)) {
double dt = Timer::GetTime() - last_time;
AccN_jerk_graph.update((mover.AccN - last_AccN) / dt);
AccN_acc_graph.update(mover.AccN);
}
last_AccN = mover.AccN;
last_time = Timer::GetTime();
}
void debug_panel::graph_data::update(float val) {
data[pos] = val;
pos++;
if (pos >= data.size())
pos = 0;
last_val = val;
}
void debug_panel::graph_data::render() {
ImGui::PushID(this);
ImGui::SliderFloat(STR_C("##Range"), &range, 0.5f, 60.0f, "%.1f");
ImGui::PlotLines("##plot", data.data(), data.size(), pos, nullptr, 0.0f, range, ImVec2(0, 100));
ImGui::PopID();
}
std::string
debug_panel::update_vehicle_coupler( int const Side ) {
// NOTE: mover and vehicle are guaranteed to be valid by the caller
auto const &mover { *m_input.mover };
std::string const controltype{ ( mover.Couplers[ Side ].control_type.empty() ? "[*]" : "[" + mover.Couplers[ Side ].control_type + "]" ) };
std::string couplerstatus { STR("none") };
std::string const adapterstatus { ( mover.Couplers[ Side ].adapter_type == TCouplerType::NoCoupler ? "" : "[A]" ) };
auto const *connected { m_input.vehicle->MoverParameters->Neighbours[ Side ].vehicle };
if( connected == nullptr ) {
return controltype + " " + couplerstatus + " " + adapterstatus;
}
std::snprintf(
m_buffer.data(), m_buffer.size(),
"%s %s %s[%d] (%.1f m)",
controltype.c_str(),
connected->name().c_str(),
adapterstatus.c_str(),
mover.Couplers[ Side ].CouplingFlag,
mover.Neighbours[ Side ].distance );
return { m_buffer.data() };
}
std::string
debug_panel::update_vehicle_brake() const {
// NOTE: mover is guaranteed to be valid by the caller
auto const &mover { *m_input.mover };
std::string brakedelay;
std::vector<std::pair<int, std::string>> delays {
{ bdelay_G, "G" },
{ bdelay_P, "P" },
{ bdelay_R, "R" },
{ bdelay_M, "+Mg" } };
for( auto const &delay : delays ) {
if( ( mover.BrakeDelayFlag & delay.first ) == delay.first ) {
brakedelay += delay.second;
}
}
return brakedelay;
}
void
debug_panel::update_section_engine( std::vector<text_line> &Output ) {
if( m_input.vehicle == nullptr ) { return; }
if( m_input.mover == nullptr ) { return; }
auto const &vehicle{ *m_input.vehicle };
auto const &mover{ *m_input.mover };
// induction motor data
if( mover.EngineType == TEngineType::ElectricInductionMotor ) {
Output.emplace_back( " eimc: eimv: press:", Global.UITextColor );
for( int i = 0; i <= 20; ++i ) {
std::string parameters =
mover.eimc_labels[ i ] + to_string( mover.eimc[ i ], 2, 9 )
+ " | "
+ mover.eimv_labels[ i ] + to_string( mover.eimv[ i ], 2, 9 );
if( i < 10 ) {
parameters +=
m_input.train != nullptr && m_input.train->Dynamic() == m_input.vehicle ? " | " + TTrain::fPress_labels[i] + to_string(m_input.train->fPress[i][0], 2, 9) : "";
}
else if( i == 12 ) {
parameters += " med:";
}
else if( i >= 13 ) {
parameters += " | " + vehicle.MED_labels[ i - 13 ] + to_string( vehicle.MED[ 0 ][ i - 13 ], 2, 9 );
}
Output.emplace_back( parameters, Global.UITextColor );
}
Output.emplace_back( "Inverter:\n frequency: " + to_string( mover.InverterFrequency, 2 ), Global.UITextColor );
}
// diesel engine data
if( mover.EngineType == TEngineType::DieselEngine ) {
double fuel_mean = 0.0;
if (mover.DistCounter > 0.5) {
fuel_mean = 100 * mover.dizel_FuelConsumptedTotal / mover.DistCounter;
}
std::string parameterstext = "param value";
std::vector< std::pair <std::string, double> > const paramvalues {
{ " rpm: ", mover.enrot * 60.0 },
{ "efill: ", mover.dizel_fill },
{ "etorq: ", mover.dizel_Torque },
{ "power: ", mover.dizel_Power },
{ "f_act: ", mover.dizel_FuelConsumptionActual },
{ "f_tot: ", mover.dizel_FuelConsumptedTotal },
{ "fmean: ", fuel_mean },
{ "creal: ", mover.dizel_engage },
{ "cdesi: ", mover.dizel_engagestate },
{ "cdelt: ", mover.dizel_engagedeltaomega },
{ "gears: ", mover.dizel_automaticgearstatus} };
for( auto const &parameter : paramvalues ) {
parameterstext += "\n" + parameter.first + to_string( parameter.second, 2, 9 );
}
Output.emplace_back( parameterstext, Global.UITextColor );
parameterstext = "hydro value";
std::vector< std::pair <std::string, double> > const hydrovalues {
{ "hTCnI: ", mover.hydro_TC_nIn },
{ "hTCnO: ", mover.hydro_TC_nOut },
{ "hTCTM: ", mover.hydro_TC_TMRatio },
{ "hTCTI: ", mover.hydro_TC_TorqueIn },
{ "hTCTO: ", mover.hydro_TC_TorqueOut },
{ "hTCfl: ", mover.hydro_TC_Fill },
{ "hRtFl: ", mover.hydro_R_Fill } ,
{ " hRtn: ", mover.hydro_R_n } ,
{ "hRtTq: ", mover.hydro_R_Torque }
};
for( auto const &parameter : hydrovalues ) {
parameterstext += "\n" + parameter.first + to_string( parameter.second, 2, 9 );
}
Output.emplace_back( parameterstext, Global.UITextColor );
}
}
void
debug_panel::update_section_ai( std::vector<text_line> &Output ) {
if( m_input.mover == nullptr ) { return; }
if( m_input.mechanik == nullptr ) { return; }
auto const &mover{ *m_input.mover };
auto const &mechanik{ *m_input.mechanik };
// biezaca komenda dla AI
auto textline =
"Current order: [" + std::to_string( mechanik.OrderPos ) + "] "
+ mechanik.Order2Str( mechanik.OrderCurrentGet() );
if( mechanik.fStopTime < 0.0 ) {
textline += "\n stop time: " + to_string( std::abs( mechanik.fStopTime ), 1 );
}
if( mechanik.fActionTime < 0.0 ) {
textline += "\n action time: " + to_string( std::abs( mechanik.fActionTime ), 1 );
}
Output.emplace_back( textline, Global.UITextColor );
if( mechanik.VelNext == 0.0
&& mechanik.eSignNext ) {
// jeśli ma zapamiętany event semafora, nazwa eventu semafora
Output.emplace_back( "Current signal: " + Bezogonkow( mechanik.eSignNext->m_name ), Global.UITextColor );
}
// distances
textline =
"Distances:\n proximity: " + to_string( mechanik.ActualProximityDist, 0 )
+ ", braking: " + to_string( mechanik.fBrakeDist, 0 );
if( mechanik.VelLimitLastDist.second > 0 ) {
textline += ", last limit: " + (
mechanik.VelLimitLastDist.second < EU07_AI_SPEEDLIMITEXTENDSBEYONDSCANRANGE ?
to_string( mechanik.VelLimitLastDist.second, 0 ) :
"???" );
}
if( mechanik.SwitchClearDist > 0 ) {
textline += ", switches: " + to_string( mechanik.SwitchClearDist, 0 );
}
if( mechanik.Obstacle.distance < 5000 ) {
textline +=
"\n obstacle: " + to_string( mechanik.Obstacle.distance, 0 )
+ " (" + mechanik.Obstacle.vehicle->asName + ")";
}
Output.emplace_back( textline, Global.UITextColor );
// velocity factors
textline =
"Velocity:\n desired: " + to_string( mechanik.VelDesired, 0 )
+ ", next: " + to_string( mechanik.VelNext, 0 )
+ "\n current: " + to_string( mover.Vel + 0.001f, 2 );
std::vector< std::pair< double, std::string > > const restrictions{
{ mechanik.VelSignalLast, "signal" },
{ mechanik.VelLimitLast, "limit" },
{ mechanik.VelRoad, "road" },
{ mechanik.VelRestricted, "restricted" },
{ mover.RunningTrack.Velmax, "track" } };
std::string restrictionstext;
for( auto const &restriction : restrictions ) {
if( restriction.first < 0.0 ) { continue; }
if( false == restrictionstext.empty() ) {
restrictionstext += ", ";
}
restrictionstext +=
to_string( restriction.first, 0 )
+ " (" + restriction.second + ")";
}
if( false == restrictionstext.empty() ) {
textline += "\n restrictions: " + restrictionstext;
}
Output.emplace_back( textline, Global.UITextColor );
// acceleration
textline =
"Acceleration:\n desired: " + to_string( mechanik.AccDesired, 2 )
+ ", corrected: " + to_string( mechanik.AccDesired * mechanik.BrakeAccFactor(), 2 )
+ "\n current: " + to_string( mechanik.AbsAccS + 0.001f, 2 )
+ ", slope: " + to_string( mechanik.fAccGravity + 0.001f, 2 ) + " (" + ( mechanik.fAccGravity > 0.01 ? "\\" : mechanik.fAccGravity < -0.01 ? "/" :
"-" ) + ")"
+ "\n desired diesel percentage: " + std::to_string(mechanik.DizelPercentage)
+ "/" + std::to_string(mechanik.DizelPercentage_Speed)
+ "/" + to_string(100.4*mechanik.mvControlling->eimic_real, 0);
Output.emplace_back( textline, Global.UITextColor );
// brakes
textline =
"Brakes:\n highest pressure: " + to_string( mechanik.fReady, 2 ) + ( mechanik.Ready ? " (all brakes released)" : "" )
+ "\n activation threshold: " + to_string( mechanik.fAccThreshold, 2 )
+ ", delays: " + to_string( mechanik.fBrake_a0[ 0 ], 2 ) + " + " + to_string( mechanik.fBrake_a1[ 0 ], 2 )
+ "\n virtual brake position: " + to_string( mechanik.BrakeCtrlPosition, 2 )
+ "\n test stage: " + std::to_string( mechanik.DynamicBrakeTest )
+ ", applied at: " + to_string( mechanik.DBT_VelocityBrake, 0 )
+ ", release at: " + to_string( mechanik.DBT_VelocityRelease, 0 )
+ ", done at: " + to_string( mechanik.DBT_VelocityFinish, 0 );
Output.emplace_back( textline, Global.UITextColor );
// ai driving flags
std::vector<std::string> const drivingflagnames {
"StopCloser", "StopPoint", "Active", "Press", "Connect", "Primary", "Late", "StopHere",
"StartHorn", "StartHornNow", "StartHornDone", "Oerlikons", "IncSpeed", "TrackEnd", "SwitchFound", "GuardSignal",
"Visibility", "DoorOpened", "PushPull", "SignalFound", "StopPointFound", "GuardOpenDoor", "DepartureWarned"
/*"SemaphorWasElapsed", "TrainInsideStation", "SpeedLimitFound"*/ };
textline = "Driving flags:";
for( int idx = 0, flagbit = 1; idx < drivingflagnames.size(); ++idx, flagbit <<= 1 ) {
if( mechanik.DrivigFlags() & flagbit ) {
textline += "\n " + drivingflagnames[ idx ];
}
}
Output.emplace_back( textline, Global.UITextColor );
}
void
debug_panel::update_section_scantable( std::vector<text_line> &Output ) {
if( m_input.mechanik == nullptr ) { return; }
Output.emplace_back( "Flags: Dist: Vel: Name:", Global.UITextColor );
auto const &mechanik{ *m_input.mechanik };
std::size_t i = 0; std::size_t const speedtablesize = std::clamp( static_cast<int>( mechanik.TableSize() ) - 1, 0, 30 );
do {
auto const scanline = mechanik.TableText( i );
if( scanline.empty() ) { break; }
Output.emplace_back( Bezogonkow( scanline ), Global.UITextColor );
++i;
} while( i < speedtablesize );
if( Output.size() == 1 ) {
Output.front().data = "(no points of interest)";
}
}
#ifdef WITH_UART
void
debug_panel::update_section_uart( std::vector<text_line> &Output ) {
uart_status *status = &UartStatus;
Output.emplace_back(
("Port: " + status->port_name).c_str(),
Global.UITextColor
);
Output.emplace_back(
("Baud: " + std::to_string(status->baud)).c_str(),
Global.UITextColor
);
if(status->is_connected) {
std::string synctext = status->is_synced ? "SYNCED" : "NOT SYNCED";
Output.emplace_back(("CONNECTED, " + synctext).c_str(), Global.UITextColor);
} else {
Output.emplace_back("* NOT CONNECTED *", Global.UITextColor);
}
Output.emplace_back(
(
"Packets sent: "+std::to_string(status->packets_sent)
+" Packets received: "+std::to_string(status->packets_received)
).c_str(),
Global.UITextColor
);
}
#endif
#ifdef WITH_HARDWARE_PROTOCOL_V2
namespace {
char const *const hardware_baudrates[] = {
"9600", "19200", "38400", "57600", "115200", "230400", "250000", "500000", "1000000", "2000000" };
} // anonymous namespace
void
debug_panel::update_section_hardware( std::vector<text_line> &Output ) {
auto const *manager = hardware::hardware_manager::instance();
if( manager == nullptr ) { return; }
// one entry per link, in the same order as the reports; render_section_hardware()
// interleaves them with the controls of each link
for( auto const &report : manager->reports() ) {
auto const &protocol = report.protocol;
std::string textline =
( report.device_name.empty() ? std::string( "(waiting for device)" ) : report.device_name )
+ ( report.device_role.empty() ? "" : " [" + report.device_role + "]" )
+ ( report.device_id.empty() ? "" : " id: " + report.device_id )
+ ( report.firmware_version.empty() ? "" : " fw: " + report.firmware_version );
textline +=
"\n" + report.transport_kind + " " + report.endpoint
+ " " + ( report.enabled ? hardware::to_string( report.state ) : "OFF" )
+ " / " + hardware::to_string( report.session );
if( report.session_id != 0 ) {
textline +=
"\nprotocol v" + std::to_string( report.protocol_major ) + "." + std::to_string( report.protocol_minor )
+ " session " + std::to_string( report.session_id )
+ " frame " + std::to_string( report.frame_size ) + " b"
+ " handles " + std::to_string( report.handles )
+ " subscriptions " + std::to_string( report.subscriptions );
}
textline +=
"\nrx " + to_string( protocol.rx_rate, 0 ) + " pkt/s"
+ " tx " + to_string( protocol.tx_rate, 0 ) + " pkt/s"
+ " rtt " + to_string( protocol.round_trip_time_ms, 1 ) + " ms"
+ " last frame " + to_string( protocol.last_rx_age, 2 ) + " s ago";
textline +=
"\nframes rx " + std::to_string( protocol.rx_frames ) + " tx " + std::to_string( protocol.tx_frames )
+ " bytes rx " + std::to_string( report.transport.rx_bytes ) + " tx " + std::to_string( report.transport.tx_bytes );
textline +=
"\nprotocol errors: crc " + std::to_string( protocol.crc_errors )
+ " frame " + std::to_string( protocol.frame_errors )
+ " length " + std::to_string( protocol.length_errors )
+ " version " + std::to_string( protocol.version_errors )
+ " gaps " + std::to_string( protocol.sequence_gaps );
textline +=
"\nunknown: message " + std::to_string( protocol.unknown_messages )
+ " symbol " + std::to_string( protocol.unknown_symbols )
+ " handle " + std::to_string( protocol.unknown_handles )
+ " duplicates " + std::to_string( protocol.duplicate_commands )
+ " rate limited " + std::to_string( protocol.rate_limited );
textline +=
"\nack " + std::to_string( protocol.acks )
+ " nack " + std::to_string( protocol.nacks )
+ " state updates " + std::to_string( protocol.state_updates )
+ " commands " + std::to_string( protocol.commands_executed )
+ " controls " + std::to_string( protocol.controls_applied );
textline +=
"\nsession uptime " + to_string( protocol.connection_uptime, 0 ) + " s"
+ " transport errors " + std::to_string( report.transport.transport_errors )
+ " reconnects " + std::to_string( report.transport.reconnects );
if( false == report.transport.last_error.empty() ) {
textline += "\nlast transport error: " + report.transport.last_error;
}
if( true == report.device.available ) {
textline +=
"\ndevice: uptime " + std::to_string( report.device.uptime_ms / 1000 ) + " s"
+ ", supply " + to_string( report.device.supply_voltage, 2 ) + " V"
+ ", temperature " + to_string( report.device.temperature, 1 ) + " deg C"
+ ", overflows rx " + std::to_string( report.device.rx_overflows ) + " tx " + std::to_string( report.device.tx_overflows )
+ ", watchdog resets " + std::to_string( report.device.watchdog_resets );
if( false == report.device.firmware_build.empty() ) {
textline += ", build " + report.device.firmware_build;
}
}
Output.emplace_back( textline, Global.UITextColor );
}
}
void
debug_panel::render_section_hardware() {
auto *manager = hardware::hardware_manager::instance();
if( manager == nullptr ) {
ImGui::TextUnformatted( "(the hardware protocol is not running)" );
return;
}
auto const &reports = manager->reports();
if( m_hardwarefunction.size() < reports.size() ) {
m_hardwarefunction.resize( reports.size(), 0 );
}
for( std::size_t index = 0; index < reports.size(); ++index ) {
auto const &report = reports[ index ];
ImGui::PushID( static_cast<int>( index ) );
ImGui::Separator();
if( index < m_hardwarelines.size() ) {
auto const &line = m_hardwarelines[ index ];
ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), "%s", line.data.c_str() );
}
// switching a link off closes its port, so the controller can be reprogrammed
if( ImGui::Button( report.enabled ? "Disconnect" : "Connect" ) ) {
manager->set_link_enabled( index, false == report.enabled );
}
ImGui::SameLine();
if( ImGui::Button( "Remove" ) ) {
manager->remove_link( index );
ImGui::PopID();
break;
}
if( false == report.functions.empty() ) {
auto &selection = m_hardwarefunction[ index ];
if( selection >= static_cast<int>( report.functions.size() ) ) { selection = 0; }
if( selection < 0 ) { selection = 0; }
std::vector<char const *> names;
names.reserve( report.functions.size() );
for( auto const &function : report.functions ) {
names.emplace_back( function.name.c_str() );
}
bool const running = ( report.diagnostic.state == hardware::diagnostic_state::running );
ImGui::SetNextItemWidth( 260 * Global.ui_scale );
ImGui::Combo( "###diagnosticfunction", &selection, names.data(), static_cast<int>( names.size() ) );
ImGui::SameLine();
if( true == running ) {
if( ImGui::Button( "Cancel" ) ) {
manager->cancel_diagnostic( index );
}
}
else {
if( ImGui::Button( "Run diagnostic" ) ) {
manager->start_diagnostic( index, report.functions[ selection ].id );
}
}
}
if( report.diagnostic.state != hardware::diagnostic_state::idle ) {
ImGui::Text(
"diagnostic %u: %s %u%% %s",
static_cast<unsigned>( report.diagnostic.function ),
hardware::to_string( report.diagnostic.state ),
static_cast<unsigned>( report.diagnostic.progress ),
report.diagnostic.message.c_str() );
}
if( false == report.log.empty() ) {
if( true == ImGui::TreeNode( "Device log" ) ) {
ImGui::BeginChild( "###devicelog", ImVec2S( 0, 140 ), true );
for( auto const &entry : report.log ) {
ImGui::Text(
"[%8.3f] %-7s %s",
entry.device_uptime_ms / 1000.0,
hardware::to_string( entry.severity ),
entry.text.c_str() );
}
ImGui::EndChild();
ImGui::TreePop();
}
}
ImGui::PopID();
}
// adding a controller which appeared after the simulation started
ImGui::Separator();
m_hardwareportnames = manager->available_ports();
m_hardwareports.clear();
for( auto const &name : m_hardwareportnames ) {
m_hardwareports.emplace_back( name.c_str() );
}
if( true == m_hardwareports.empty() ) {
ImGui::TextUnformatted( "(no serial ports detected)" );
}
else {
if( m_hardwareport >= static_cast<int>( m_hardwareports.size() ) ) { m_hardwareport = -1; }
ImGui::SetNextItemWidth( 140 * Global.ui_scale );
ImGui::Combo( "###hardwareport", &m_hardwareport, m_hardwareports.data(), static_cast<int>( m_hardwareports.size() ) );
ImGui::SameLine();
ImGui::SetNextItemWidth( 120 * Global.ui_scale );
ImGui::Combo( "###hardwarebaud", &m_hardwarebaud, hardware_baudrates, static_cast<int>( sizeof( hardware_baudrates ) / sizeof( hardware_baudrates[ 0 ] ) ) );
ImGui::SameLine();
if( ImGui::Button( "Add controller" ) ) {
if( ( m_hardwareport >= 0 ) && ( m_hardwareport < static_cast<int>( m_hardwareportnames.size() ) ) ) {
manager->add_serial_link( m_hardwareportnames[ m_hardwareport ], std::atoi( hardware_baudrates[ m_hardwarebaud ] ) );
}
}
}
ImGui::Separator();
bool logmessages = hardware::debug_flags.log_messages;
if( ImGui::Checkbox( "Log protocol messages", &logmessages ) ) {
hardware::debug_flags.log_messages = logmessages;
}
bool logframes = hardware::debug_flags.log_frames;
if( ImGui::Checkbox( "Log every frame", &logframes ) ) {
hardware::debug_flags.log_frames = logframes;
}
}
void
debug_panel::render_hardware_prompt() {
auto *manager = hardware::hardware_manager::instance();
if( manager == nullptr ) { return; }
for( auto const &report : manager->reports() ) {
if( false == report.prompt.active ) { continue; }
auto const title =
( report.device_name.empty() ? std::string( "Controller" ) : report.device_name )
+ "###hardwareprompt" + std::to_string( report.index );
bool open = true;
ImGui::SetNextWindowSize( ImVec2S( 420, 0 ), ImGuiCond_Appearing );
if( true == ImGui::Begin( title.c_str(), &open, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_AlwaysAutoResize ) ) {
ImGui::TextWrapped( "%s", report.prompt.text.c_str() );
ImGui::Separator();
for( std::size_t option = 0; option < report.prompt.options.size(); ++option ) {
if( option > 0 ) { ImGui::SameLine(); }
ImGui::PushID( static_cast<int>( option ) );
if( ImGui::Button( report.prompt.options[ option ].c_str() ) ) {
manager->answer_prompt( report.index, static_cast<std::uint8_t>( option ) );
}
ImGui::PopID();
}
}
ImGui::End();
if( false == open ) {
// closing the window without choosing tells the controller the user walked away
manager->answer_prompt( report.index, hardware::prompt_dismissed );
}
}
}
#endif
void
debug_panel::update_section_scenario( std::vector<text_line> &Output ) {
auto textline =
"vehicles: " + to_string( Timer::subsystem.sim_dynamics.average(), 2 ) + " msec"
+ " update total: " + to_string( Timer::subsystem.sim_total.average(), 2 ) + " msec";
Output.emplace_back( textline, Global.UITextColor );
// current luminance level
textline = "Light level: " + to_string( Global.fLuminance, 3 ) + ( Global.FakeLight ? "(*)" : "" )+ to_string(Global.SunAngle,2);
textline +=
"\nWind: azimuth "
+ to_string( simulation::Environment.wind_azimuth(), 0 ) // ma być azymut, czyli 0 na północy i rośnie na wschód
+ " "
+ std::string( "N NEE SES SWW NW" )
.substr( 0 + 2 * std::floor( std::fmod( 8 + ( glm::radians( simulation::Environment.wind_azimuth() ) + 0.5 * M_PI_4 ) / M_PI_4, 8 ) ), 2 )
+ ", " + to_string( glm::length( simulation::Environment.wind() ), 1 ) + " m/s";
textline += "\nAir temperature: " + to_string( Global.AirTemperature, 1 ) + " deg C";
Output.emplace_back( textline, Global.UITextColor );
}
void
debug_panel::update_section_eventqueue( std::vector<text_line> &Output ) {
std::string textline;
// current event queue
auto const time { Timer::GetTime() };
auto const *event { simulation::Events.begin() };
auto const searchfilter { std::string( m_eventsearch.data() ) };
Output.emplace_back( "Delay: Event:", Global.UITextColor );
while( event != nullptr
&& Output.size() < 30 ) {
if( false == event->m_ignored
&& false == event->m_passive
&& ( false == m_eventqueueactivevehicleonly
|| event->m_activator == m_input.vehicle ) ) {
auto const label { event->m_name + ( event->m_activator ? " (by: " + event->m_activator->asName + ")" : "" ) };
if( false == searchfilter.empty()
&& false == contains(label, searchfilter) ) {
event = event->m_next;
continue;
}
auto const delay { " " + to_string( std::max( 0.0, event->m_launchtime - time ), 1 ) };
textline =
delay.substr( delay.length() - 6 )
+ " "
+ label + ( event->m_sibling ? " (joint event)" : "" );
Output.emplace_back( textline, Global.UITextColor );
}
event = event->m_next;
}
if( Output.size() == 1 ) {
// event queue can be empty either because no event got through active filters, or because it is genuinely empty
Output.front().data = simulation::Events.begin() == nullptr ? "(no queued events)" : "(no matching events)";
}
}
void
debug_panel::update_section_powergrid( std::vector<text_line> &Output ) {
auto const lowpowercolor { glm::vec4( 164.0f / 255.0f, 132.0f / 255.0f, 84.0f / 255.0f, 1.f ) };
auto const nopowercolor { glm::vec4( 164.0f / 255.0f, 84.0f / 255.0f, 84.0f / 255.0f, 1.f ) };
Output.emplace_back( "Name: Output: Current: Timeout:", Global.UITextColor );
std::string textline;
for( auto const *powerstation : simulation::Powergrid.sequence() ) {
if( true == powerstation->IsAutogenerated ) { continue; }
if( true == powerstation->bSection ) { continue; }
auto const name { (
powerstation->m_name.empty() ?
"(unnamed)" :
powerstation->m_name )
+ " " };
textline =
name.substr( 0, 20 )
+ " " + to_string( powerstation->OutputVoltage, 0, 5 )
+ " " + to_string( powerstation->TotalCurrent, 1, 8 )
+ " " + to_string( powerstation->FuseTimer, 1, 8 )
+ ( powerstation->FuseCounter == 0 ?
"" :
" (x" + std::to_string( powerstation->FuseCounter ) + ")" );
Output.emplace_back(
textline,
powerstation->FastFuse || powerstation->SlowFuse ? nopowercolor :
powerstation->OutputVoltage < 0.8 * powerstation->NominalVoltage ? lowpowercolor :
Global.UITextColor );
}
if( Output.size() == 1 ) {
Output.front().data = "(no power stations)";
}
}
void
debug_panel::update_section_camera( std::vector<text_line> &Output ) {
if( m_input.camera == nullptr ) { return; }
auto const &camera{ *m_input.camera };
// camera data
auto textline =
"Position: ["
+ to_string( camera.Pos.x, 2 ) + ", "
+ to_string( camera.Pos.y, 2 ) + ", "
+ to_string( camera.Pos.z, 2 ) + "]";
Output.emplace_back( textline, Global.UITextColor );
textline =
"Azimuth: "
+ to_string( 180.0 - glm::degrees( camera.Angle.y ), 0 ) // ma być azymut, czyli 0 na północy i rośnie na wschód
+ " "
+ std::string( "S SEE NEN NWW SW" )
.substr( 0 + 2 * floor( fmod( 8 + ( camera.Angle.y + 0.5 * M_PI_4 ) / M_PI_4, 8 ) ), 2 );
Output.emplace_back( textline, Global.UITextColor );
}
void
debug_panel::update_section_renderer( std::vector<text_line> &Output ) {
// gfx renderer data
auto textline =
"FoV: " + to_string( Global.FieldOfView / Global.ZoomFactor, 1 )
+ ", Draw range: " + to_string( Global.BaseDrawRange * Global.fDistanceFactor, 0 ) + "m"
// + "; sectors: " + std::to_string( GfxRenderer->m_drawcount )
// + ", FPS: " + to_string( Timer::GetFPS(), 2 );
+ ", FPS: " + std::to_string( static_cast<int>(std::round(GfxRenderer->Framerate())) )
+ ( Global.VSync ? " (vsync on)" : "" );
if( Global.iSlowMotion ) {
textline += " (slowmotion " + std::to_string( Global.iSlowMotion ) + ")";
}
Output.emplace_back( textline, Global.UITextColor );
textline += "\nRendering mode: ";
if (Global.GfxRenderer == "default")
{
textline += "Shaders";
}
else if (Global.GfxRenderer == "experimental")
{
textline += "NVRHI on ";
}
else
{
if (Global.BasicRenderer)
{
textline += "Legacy Simple";
}
else
{
textline += "Legacy";
}
}
if (Global.bUseVBO)
{
textline += ", VBO";
}
else
{
textline += ", Display Lists";
}
textline += " ";
if( false == Global.LastGLError.empty() ) {
textline +=
"Last openGL error: "
+ Global.LastGLError;
}
Output.emplace_back( textline, Global.UITextColor );
// renderer stats
Output.emplace_back( GfxRenderer->info_times(), Global.UITextColor );
Output.emplace_back( GfxRenderer->info_stats(), Global.UITextColor );
// CPU related
Output.emplace_back("CPU:", Global.UITextColor);
// thread counter
textline = "Running threads: " + std::to_string(Global.threads.size() + 1);
Output.emplace_back(textline, Global.UITextColor);
}
bool
debug_panel::render_section( std::string const &Header, std::vector<text_line> const &Lines ) {
if( true == Lines.empty() ) { return false; }
if( false == ImGui::CollapsingHeader( Header.c_str() ) ) { return false; }
return render_section( Lines );
}
bool
debug_panel::render_section( std::vector<text_line> const &Lines ) {
for( auto const &line : Lines ) {
ImGui::PushStyleColor( ImGuiCol_Text, { line.color.r, line.color.g, line.color.b, line.color.a } );
ImGui::TextUnformatted( line.data.c_str() );
ImGui::PopStyleColor();
// ImGui::TextColored( ImVec4( line.color.r, line.color.g, line.color.b, line.color.a ), line.data.c_str() );
}
return true;
}
bool debug_panel::render_section_developer()
{
if (false == ImGui::CollapsingHeader("Developer tools"))
return false;
ImGui::PushStyleColor(ImGuiCol_Text, {Global.UITextColor.r, Global.UITextColor.g, Global.UITextColor.b, Global.UITextColor.a});
ImGui::TextUnformatted("Warning! These tools are only for developers.\nDo not use them if you are NOT sure what they do!");
ImGui::TextColored(ImVec4(1.0f, 0.0f, 0.0f, 1.0f), "These settings may crash your simulator!");
if (ImGui::Button("Reload current vehicle .fiz") == true)
{
m_input.vehicle->MoverParameters->reload_FIZ(); // reload fiz
}
ImGui::PopStyleColor();
return true;
}
bool
debug_panel::render_section_settings() {
if( false == ImGui::CollapsingHeader( "Settings" ) ) { return false; }
ImGui::PushStyleColor( ImGuiCol_Text, { Global.UITextColor.r, Global.UITextColor.g, Global.UITextColor.b, Global.UITextColor.a } );
ImGui::TextUnformatted( "Graphics" );
ImGui::PopStyleColor();
// reflection fidelity
ImGui::SliderInt( ( std::to_string( Global.reflectiontune.fidelity ) + "###reflectionfidelity" ).c_str(), &Global.reflectiontune.fidelity, 0, 2, "Reflection fidelity" );
ImGui::SliderInt( ( std::to_string( Global.gfx_shadow_rank_cutoff ) + "###shadowrankcutoff" ).c_str(), &Global.gfx_shadow_rank_cutoff, 1, 3, "Shadow ranks" );
if( ImGui::SliderFloat( ( to_string( std::abs( Global.gfx_shadow_angle_min ), 2 ) + "###shadowanglecutoff" ).c_str(), &Global.gfx_shadow_angle_min, -1.0, -0.2, "Shadow angle cutoff" ) ) {
Global.gfx_shadow_angle_min = quantize( Global.gfx_shadow_angle_min, 0.05f );
};
if( DebugModeFlag ) {
// sky sliders
{
ImGui::SliderFloat(
( to_string( Global.m_skysaturationcorrection, 2, 5 ) + "###skysaturation" ).c_str(), &Global.m_skysaturationcorrection, 0.0f, 3.0f, "Sky saturation" );
}
{
ImGui::SliderFloat(
( to_string( Global.m_skyhuecorrection, 2, 5 ) + "###skyhue" ).c_str(), &Global.m_skyhuecorrection, 0.0f, 1.0f, "Sky hue correction" );
}
}
ImGui::PushStyleColor( ImGuiCol_Text, { Global.UITextColor.r, Global.UITextColor.g, Global.UITextColor.b, Global.UITextColor.a } );
ImGui::TextUnformatted( "Sound" );
ImGui::PopStyleColor();
// audio volume sliders
ImGui::SliderFloat( ( std::to_string( static_cast<int>( Global.AudioVolume * 100 ) ) + "%###volumemain" ).c_str(), &Global.AudioVolume, 0.0f, 2.0f, "Main audio volume" );
if( ImGui::SliderFloat( ( std::to_string( static_cast<int>( Global.VehicleVolume * 100 ) ) + "%###volumevehicle" ).c_str(), &Global.VehicleVolume, 0.0f, 1.0f, "Vehicle sounds" ) ) {
audio::event_volume_change = true;
}
if( ImGui::SliderFloat( ( std::to_string( static_cast<int>( Global.EnvironmentPositionalVolume * 100 ) ) + "%###volumepositional" ).c_str(), &Global.EnvironmentPositionalVolume, 0.0f, 1.0f, "Positional sounds" ) ) {
audio::event_volume_change = true;
}
if( ImGui::SliderFloat( ( std::to_string( static_cast<int>( Global.EnvironmentAmbientVolume * 100 ) ) + "%###volumeambient" ).c_str(), &Global.EnvironmentAmbientVolume, 0.0f, 1.0f, "Ambient sounds" ) ) {
audio::event_volume_change = true;
}
if (simulation::Train) {
float val = simulation::Train->get_radiovolume();
if( ImGui::SliderFloat( ( std::to_string( static_cast<int>( val * 100 ) ) + "%###volumeradio" ).c_str(), &val, 0.0f, 1.0f, "Vehicle radio volume" ) ) {
command_relay relay;
relay.post(user_command::radiovolumeset, val, 0.0, GLFW_PRESS, 0);
}
}
return true;
}
void
transcripts_panel::update() {
if( false == is_open ) { return; }
text_lines.clear();
for( auto const &transcript : ui::Transcripts.aLines ) {
if( Global.fTimeAngleDeg + ( transcript.fShow - Global.fTimeAngleDeg > 180 ? 360 : 0 ) < transcript.fShow ) { continue; }
std::string text = transcript.asText;
std::ranges::replace(text, '|', ' ');
text_lines.emplace_back( text, colors::white );
}
}
void
transcripts_panel::render() {
if( false == is_open ) { return; }
if( true == text_lines.empty() ) { return; }
auto flags =
ImGuiWindowFlags_NoFocusOnAppearing
| ImGuiWindowFlags_NoCollapse
| ( size.x > 0 ? ImGuiWindowFlags_NoResize : 0 );
if( size.x > 0 ) {
ImGui::SetNextWindowSize( ImVec2S( size.x, size.y ) );
}
if( size_min.x > 0 ) {
ImGui::SetNextWindowSizeConstraints( ImVec2S( size_min.x, size_min.y ), ImVec2S( size_max.x, size_max.y ) );
}
auto const panelname { (
title.empty() ?
m_name :
title )
+ "###" + m_name };
if( true == ImGui::Begin( panelname.c_str(), &is_open, flags ) ) {
// header section
for( auto const &line : text_lines ) {
ImGui::TextWrapped( "%s", line.data.c_str() );
}
}
ImGui::End();
}