diff --git a/CMakeLists.txt b/CMakeLists.txt index cb1ae54f..d7e285c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -127,6 +127,7 @@ set(SOURCES "widgets/map.cpp" "widgets/map_objects.cpp" "widgets/popup.cpp" +"widgets/time.cpp" "ref/glad/src/glad.c" diff --git a/Globals.h b/Globals.h index 08be5c64..53f490ab 100644 --- a/Globals.h +++ b/Globals.h @@ -29,6 +29,7 @@ struct global_settings { std::mt19937 random_engine; std::mt19937 local_random_engine; bool ready_to_load { false }; + std::time_t starting_timestamp = 0; // starting time, in local timezone uint32_t random_seed = 0; TCamera pCamera; // parametry kamery TCamera pDebugCamera; @@ -73,8 +74,7 @@ struct global_settings { float FrictionWeatherFactor { 1.f }; bool bLiveTraction{ true }; float Overcast{ 0.1f }; // NOTE: all this weather stuff should be moved elsewhere - glm::vec3 FogColor = { 0.6f, 0.7f, 0.8f }; - double fFogStart{ 1700 }; + glm::vec3 FogColor = { 0.6f, 0.7f, 0.8f }; double fFogEnd{ 2000 }; std::string Season{}; // season of the year, based on simulation date std::string Weather{ "cloudy:" }; // current weather diff --git a/Timer.cpp b/Timer.cpp index 478b42d0..278a879a 100644 --- a/Timer.cpp +++ b/Timer.cpp @@ -63,7 +63,7 @@ void UpdateTimers(bool pause) QueryPerformanceCounter((LARGE_INTEGER *)&count); #elif __linux__ timespec ts; - clock_gettime(CLOCK_MONOTONIC_RAW, &ts); + clock_gettime(CLOCK_MONOTONIC, &ts); count = (uint64_t)ts.tv_sec * 1000000000 + (uint64_t)ts.tv_nsec; fr = 1000000000; #endif diff --git a/application.cpp b/application.cpp index e3fbac4d..9298af12 100644 --- a/application.cpp +++ b/application.cpp @@ -767,6 +767,12 @@ bool eu07_application::init_network() { if (!Global.random_seed) Global.random_seed = std::random_device{}(); Global.random_engine.seed(Global.random_seed); + + // TODO: sort out this timezone mess + std::time_t utc_now = std::time(nullptr); + std::time_t local_now = utc_now + (std::mktime(std::localtime(&utc_now)) - std::mktime(std::gmtime(&utc_now))); + + Global.starting_timestamp = local_now; Global.ready_to_load = true; } diff --git a/command.cpp b/command.cpp index 0b28391b..d63d134f 100644 --- a/command.cpp +++ b/command.cpp @@ -227,6 +227,9 @@ commanddescription_sequence Commands_descriptions = { { "timejump", command_target::simulation, command_mode::oneoff }, { "timejumplarge", command_target::simulation, command_mode::oneoff }, { "timejumpsmall", command_target::simulation, command_mode::oneoff }, + { "setdatetime", command_target::simulation, command_mode::oneoff }, + { "setweather", command_target::simulation, command_mode::oneoff }, + { "settemperature", command_target::simulation, command_mode::oneoff }, { "vehiclemove", command_target::vehicle, command_mode::oneoff }, { "vehiclemoveforwards", command_target::vehicle, command_mode::oneoff }, { "vehiclemovebackwards", command_target::vehicle, command_mode::oneoff }, diff --git a/command.h b/command.h index 8b34478c..c0c9ace6 100644 --- a/command.h +++ b/command.h @@ -221,6 +221,9 @@ enum class user_command { timejump, timejumplarge, timejumpsmall, + setdatetime, + setweather, + settemperature, vehiclemove, vehiclemoveforwards, vehiclemovebackwards, diff --git a/driveruilayer.cpp b/driveruilayer.cpp index 9e9b8b92..68c462d3 100644 --- a/driveruilayer.cpp +++ b/driveruilayer.cpp @@ -28,6 +28,7 @@ driver_ui::driver_ui() { push_back( &m_transcriptspanel ); //push_back( &m_vehiclelist ); + push_back( &m_timepanel ); push_back( &m_mappanel ); push_back( &m_logpanel ); m_logpanel.is_open = false; @@ -52,6 +53,10 @@ void driver_ui::render_menu_contents() { ImGui::MenuItem(m_timetablepanel.title.c_str(), "F2", &m_timetablepanel.is_open); ImGui::MenuItem(m_debugpanel.get_name().c_str(), "F12", &m_debugpanel.is_open); ImGui::MenuItem(m_mappanel.get_name().c_str(), "Tab", &m_mappanel.is_open); + + if (ImGui::MenuItem(m_timepanel.get_name().c_str())) + m_timepanel.open(); + ImGui::EndMenu(); } } diff --git a/driveruilayer.h b/driveruilayer.h index 9ddf8101..d98f43c0 100644 --- a/driveruilayer.h +++ b/driveruilayer.h @@ -15,6 +15,7 @@ http://mozilla.org/MPL/2.0/. #include "widgets/vehiclelist.h" #include "widgets/map.h" +#include "widgets/time.h" class driver_ui : public ui_layer { @@ -58,4 +59,5 @@ private: command_relay m_relay; ui::vehiclelist_panel m_vehiclelist; ui::map_panel m_mappanel; + ui::time_panel m_timepanel; }; diff --git a/network/message.cpp b/network/message.cpp index 418a891e..d404b2c0 100644 --- a/network/message.cpp +++ b/network/message.cpp @@ -16,11 +16,13 @@ void network::client_hello::deserialize(std::istream &stream) void network::server_hello::serialize(std::ostream &stream) const { sn_utils::ls_uint32(stream, seed); + sn_utils::ls_int64(stream, timestamp); } void network::server_hello::deserialize(std::istream &stream) { seed = sn_utils::ld_uint32(stream); + timestamp = sn_utils::ld_int64(stream); } void ::network::request_command::serialize(std::ostream &stream) const diff --git a/network/message.h b/network/message.h index 8d2827bd..aa81a5ed 100644 --- a/network/message.h +++ b/network/message.h @@ -39,6 +39,7 @@ struct server_hello : public message server_hello() : message(SERVER_HELLO) {} uint32_t seed; + int64_t timestamp; virtual void serialize(std::ostream &stream) const override; virtual void deserialize(std::istream &stream) override; diff --git a/network/network.cpp b/network/network.cpp index 030e1b59..0d9088c3 100644 --- a/network/network.cpp +++ b/network/network.cpp @@ -122,6 +122,7 @@ void network::server::handle_message(std::shared_ptr conn, const mes server_hello reply; reply.seed = Global.random_seed; + reply.timestamp = Global.starting_timestamp; conn->state = connection::CATCHING_UP; conn->backbuffer = backbuffer; conn->backbuffer_pos = 0; @@ -229,6 +230,7 @@ void network::client::handle_message(std::shared_ptr conn, const mes if (!Global.ready_to_load) { Global.random_seed = cmd.seed; Global.random_engine.seed(Global.random_seed); + Global.starting_timestamp = cmd.timestamp; Global.ready_to_load = true; } else if (Global.random_seed != cmd.seed) { ErrorLog("net: seed mismatch", logtype::net); diff --git a/precipitation.cpp b/precipitation.cpp index 80b8240b..19eb0695 100644 --- a/precipitation.cpp +++ b/precipitation.cpp @@ -90,24 +90,28 @@ basic_precipitation::init() { create( 18 ); - // TODO: select texture based on current overcast level - // TODO: when the overcast level dynamic change is in check the current level during render and pick the appropriate texture on the fly - std::string const densitysuffix { ( - Global.Overcast < 1.35 ? - "_light" : - "_medium" ) }; - if( Global.Weather == "rain:" ) { - m_moverateweathertypefactor = 2.f; - m_texture = GfxRenderer.Fetch_Texture( "fx/rain" + densitysuffix ); - } - else if( Global.Weather == "snow:" ) { - m_moverateweathertypefactor = 1.25f; - m_texture = GfxRenderer.Fetch_Texture( "fx/snow" + densitysuffix ); - } - return true; } +void +basic_precipitation::update_weather() { + + // TODO: select texture based on current overcast level + // TODO: when the overcast level dynamic change is in check the current level during render and pick the appropriate texture on the fly + std::string const densitysuffix { ( + Global.Overcast < 1.35 ? + "_light" : + "_medium" ) }; + if( Global.Weather == "rain:" ) { + m_moverateweathertypefactor = 2.f; + m_texture = GfxRenderer.Fetch_Texture( "fx/rain" + densitysuffix ); + } + else if( Global.Weather == "snow:" ) { + m_moverateweathertypefactor = 1.25f; + m_texture = GfxRenderer.Fetch_Texture( "fx/snow" + densitysuffix ); + } +} + void basic_precipitation::update() { diff --git a/precipitation.h b/precipitation.h index 34b74408..31c10076 100644 --- a/precipitation.h +++ b/precipitation.h @@ -26,11 +26,14 @@ public: // methods bool init(); + void + update_weather(); void update(); void render(); - float get_textureoffset(); + float + get_textureoffset(); glm::dvec3 m_cameramove{ 0.0 }; diff --git a/renderer.cpp b/renderer.cpp index 8feefd70..8d6c7ff0 100644 --- a/renderer.cpp +++ b/renderer.cpp @@ -195,9 +195,26 @@ bool opengl_renderer::Init(GLFWwindow *Window) m_empty_cubemap = std::make_unique(); m_empty_cubemap->alloc(Global.gfx_format_color, 16, 16, GL_RGB, GL_FLOAT); - m_current_viewport = &default_viewport; - if (!init_viewport(default_viewport)) - return false; + m_viewports.push_back(std::make_unique()); + viewport_config &default_viewport = *m_viewports.front().get(); + default_viewport.width = Global.gfx_framebuffer_width; + default_viewport.height = Global.gfx_framebuffer_height; + + /* + default_viewport.camera_transform = glm::rotate(glm::mat4(), 0.5f, glm::vec3(1.0f, 0.0f, 0.f)); + + m_viewports.push_back(std::make_unique()); + viewport_config &vp2 = *m_viewports.back().get(); + vp2.width = Global.gfx_framebuffer_width; + vp2.height = Global.gfx_framebuffer_height; + + vp2.camera_transform = glm::rotate(glm::mat4(), -0.5f, glm::vec3(1.0f, 0.0f, 0.f)); + */ + + for (auto &viewport : m_viewports) { + if (!init_viewport(*viewport.get())) + return false; + } m_pick_tex = std::make_unique(); m_pick_tex->alloc_rendertarget(GL_RGB8, GL_RGB, EU07_PICKBUFFERSIZE, EU07_PICKBUFFERSIZE); @@ -271,17 +288,17 @@ bool opengl_renderer::Init(GLFWwindow *Window) return true; } -bool opengl_renderer::init_viewport(viewport_data &vp) +bool opengl_renderer::init_viewport(viewport_config &vp) { int samples = 1 << Global.iMultisampling; if (!Global.gfx_skippipeline) { vp.msaa_rbc = std::make_unique(); - vp.msaa_rbc->alloc(Global.gfx_format_color, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height, samples); + vp.msaa_rbc->alloc(Global.gfx_format_color, vp.width, vp.height, samples); vp.msaa_rbd = std::make_unique(); - vp.msaa_rbd->alloc(Global.gfx_format_depth, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height, samples); + vp.msaa_rbd->alloc(Global.gfx_format_depth, vp.width, vp.height, samples); vp.msaa_fb = std::make_unique(); vp.msaa_fb->attach(*vp.msaa_rbc, GL_COLOR_ATTACHMENT0); @@ -290,17 +307,17 @@ bool opengl_renderer::init_viewport(viewport_data &vp) if (Global.gfx_postfx_motionblur_enabled) { vp.msaa_rbv = std::make_unique(); - vp.msaa_rbv->alloc(Global.gfx_postfx_motionblur_format, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height, samples); + vp.msaa_rbv->alloc(Global.gfx_postfx_motionblur_format, vp.width, vp.height, samples); vp.msaa_fb->attach(*vp.msaa_rbv, GL_COLOR_ATTACHMENT1); vp.main_tex = std::make_unique(); - vp.main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height, 1, GL_CLAMP_TO_EDGE); + vp.main_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, vp.width, vp.height, 1, GL_CLAMP_TO_EDGE); vp.main_fb = std::make_unique(); vp.main_fb->attach(*vp.main_tex, GL_COLOR_ATTACHMENT0); vp.main_texv = std::make_unique(); - vp.main_texv->alloc_rendertarget(Global.gfx_postfx_motionblur_format, GL_RG, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height); + vp.main_texv->alloc_rendertarget(Global.gfx_postfx_motionblur_format, GL_RG, vp.width, vp.height); vp.main_fb->attach(*vp.main_texv, GL_COLOR_ATTACHMENT1); vp.main_fb->setup_drawing(2); @@ -314,7 +331,7 @@ bool opengl_renderer::init_viewport(viewport_data &vp) return false; vp.main2_tex = std::make_unique(); - vp.main2_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, Global.gfx_framebuffer_width, Global.gfx_framebuffer_height); + vp.main2_tex->alloc_rendertarget(Global.gfx_format_color, GL_RGB, vp.width, vp.height); vp.main2_fb = std::make_unique(); vp.main2_fb->attach(*vp.main2_tex, GL_COLOR_ATTACHMENT0); @@ -412,7 +429,10 @@ bool opengl_renderer::Render() m_renderpass.draw_mode = rendermode::none; // force setup anew m_debugstats = debug_stats(); - Render_pass(default_viewport, rendermode::color); + for (auto &viewport : m_viewports) { + Render_pass(*viewport.get(), rendermode::color); + } + //Render_pass(*m_viewports.front().get(), rendermode::color); m_drawcount = m_cellqueue.size(); m_debugtimestext.clear(); @@ -475,9 +495,9 @@ void opengl_renderer::draw_debug_ui() } // runs jobs needed to generate graphics for specified render pass -void opengl_renderer::Render_pass(viewport_data &vp, rendermode const Mode) +void opengl_renderer::Render_pass(viewport_config &vp, rendermode const Mode) { - setup_pass(m_renderpass, Mode); + setup_pass(vp, m_renderpass, Mode); switch (m_renderpass.draw_mode) { @@ -520,7 +540,7 @@ void opengl_renderer::Render_pass(viewport_data &vp, rendermode const Mode) Render_pass(vp, rendermode::shadows); if (!FreeFlyModeFlag) Render_pass(vp, rendermode::cabshadows); - setup_pass(m_renderpass, Mode); // restore draw mode. TBD, TODO: render mode stack + setup_pass(vp, m_renderpass, Mode); // restore draw mode. TBD, TODO: render mode stack Timer::subsystem.gfx_shadows.stop(); glDebug("render shadowmap end"); @@ -530,7 +550,7 @@ void opengl_renderer::Render_pass(viewport_data &vp, rendermode const Mode) { // potentially update environmental cube map if (Render_reflections(vp)) - setup_pass(m_renderpass, Mode); // restore color pass settings + setup_pass(vp, m_renderpass, Mode); // restore color pass settings setup_env_map(vp.env_tex.get()); } @@ -851,7 +871,7 @@ void opengl_renderer::Render_pass(viewport_data &vp, rendermode const Mode) } // creates dynamic environment cubemap -bool opengl_renderer::Render_reflections(viewport_data &vp) +bool opengl_renderer::Render_reflections(viewport_config &vp) { if (Global.ReflectionUpdatesPerSecond == 0) return false; @@ -943,7 +963,8 @@ glm::mat4 opengl_renderer::ortho_frustumtest_projection(float l, float r, float return glm::ortho(l, r, b, t, znear, zfar); } -void opengl_renderer::setup_pass(renderpass_config &Config, rendermode const Mode, float const Znear, float const Zfar, bool const Ignoredebug) +void opengl_renderer::setup_pass(viewport_config &Viewport, renderpass_config &Config, rendermode const Mode, + float const Znear, float const Zfar, bool const Ignoredebug) { Config.draw_mode = Mode; @@ -1001,10 +1022,14 @@ void opengl_renderer::setup_pass(renderpass_config &Config, rendermode const Mod float const fovy = glm::radians(Global.FieldOfView / Global.ZoomFactor); float const aspect = std::max(1.f, (float)Global.iWindowWidth) / std::max(1.f, (float)Global.iWindowHeight); + Config.viewport_camera.position() = Global.pCamera.Pos; + switch (Mode) { case rendermode::color: { + viewmatrix = glm::dmat4(Viewport.camera_transform); + // modelview if ((false == DebugCameraFlag) || (true == Ignoredebug)) { @@ -1016,6 +1041,7 @@ void opengl_renderer::setup_pass(renderpass_config &Config, rendermode const Mod camera.position() = Global.pDebugCamera.Pos; Global.pDebugCamera.SetMatrix(viewmatrix); } + // projection auto const zfar = Config.draw_range * Global.fDistanceFactor * Zfar; auto const znear = (Znear > 0.f ? Znear * zfar : 0.1f * Global.ZoomFactor); @@ -1030,7 +1056,7 @@ void opengl_renderer::setup_pass(renderpass_config &Config, rendermode const Mod // ...setup chunk of frustum we're interested in... auto const zfar = std::min(1.f, Global.shadowtune.depth / (Global.BaseDrawRange * Global.fDistanceFactor) * std::max(1.f, Global.ZoomFactor * 0.5f)); renderpass_config worldview; - setup_pass(worldview, rendermode::color, 0.f, zfar, true); + setup_pass(Viewport, worldview, rendermode::color, 0.f, zfar, true); auto &frustumchunkshapepoints = worldview.pass_camera.frustum_points(); // ...modelview matrix: determine the centre of frustum chunk in world space... glm::vec3 frustumchunkmin, frustumchunkmax; @@ -1110,9 +1136,12 @@ void opengl_renderer::setup_pass(renderpass_config &Config, rendermode const Mod case rendermode::pickcontrols: case rendermode::pickscenery: { + viewmatrix = glm::dmat4(Viewport.camera_transform); + // modelview camera.position() = Global.pCamera.Pos; Global.pCamera.SetMatrix(viewmatrix); + // projection float znear = 0.1f * Global.ZoomFactor; float zfar = Config.draw_range * Global.fDistanceFactor; @@ -1998,7 +2027,7 @@ void opengl_renderer::Render(scene::shape_node const &Shape, bool const Ignorera case rendermode::shadows: { // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees - distancesquared = Math3D::SquareMagnitude((data.area.center - Global.pCamera.Pos) / Global.ZoomFactor) / Global.fDistanceFactor; + distancesquared = Math3D::SquareMagnitude((data.area.center - m_renderpass.viewport_camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor; break; } default: @@ -2052,7 +2081,7 @@ void opengl_renderer::Render(TAnimModel *Instance) case rendermode::shadows: { // 'camera' for the light pass is the light source, but we need to draw what the 'real' camera sees - distancesquared = Math3D::SquareMagnitude((Instance->location() - Global.pCamera.Pos) / Global.ZoomFactor) / Global.fDistanceFactor; + distancesquared = Math3D::SquareMagnitude((Instance->location() - m_renderpass.viewport_camera.position()) / (double)Global.ZoomFactor) / Global.fDistanceFactor; break; } default: @@ -2110,7 +2139,7 @@ bool opengl_renderer::Render(TDynamicObject *Dynamic) { case rendermode::shadows: { - squaredistance = glm::length2(glm::vec3{glm::dvec3{Dynamic->vPosition - Global.pCamera.Pos}} / Global.ZoomFactor) / Global.fDistanceFactor; + squaredistance = glm::length2(glm::vec3{glm::dvec3{Dynamic->vPosition - m_renderpass.viewport_camera.position()}} / Global.ZoomFactor) / Global.fDistanceFactor; break; } default: @@ -3430,7 +3459,7 @@ void opengl_renderer::Update_Pick_Control() pickbufferpos = glm::ivec2{mousepos.x * EU07_PICKBUFFERSIZE / std::max(1, Global.iWindowWidth), mousepos.y * EU07_PICKBUFFERSIZE / std::max(1, Global.iWindowHeight)}; pickbufferpos = glm::clamp(pickbufferpos, glm::ivec2(0, 0), glm::ivec2(EU07_PICKBUFFERSIZE - 1, EU07_PICKBUFFERSIZE - 1)); - Render_pass(default_viewport, rendermode::pickcontrols); + Render_pass(*m_viewports.front().get(), rendermode::pickcontrols); m_pick_fb->bind(); m_picking_pbo->request_read(pickbufferpos.x, pickbufferpos.y, 1, 1); m_pick_fb->unbind(); @@ -3469,7 +3498,7 @@ void opengl_renderer::Update_Pick_Node() pickbufferpos = glm::ivec2{mousepos.x * EU07_PICKBUFFERSIZE / std::max(1, Global.iWindowWidth), mousepos.y * EU07_PICKBUFFERSIZE / std::max(1, Global.iWindowHeight)}; pickbufferpos = glm::clamp(pickbufferpos, glm::ivec2(0, 0), glm::ivec2(EU07_PICKBUFFERSIZE - 1, EU07_PICKBUFFERSIZE - 1)); - Render_pass(default_viewport, rendermode::pickscenery); + Render_pass(*m_viewports.front().get(), rendermode::pickscenery); m_pick_fb->bind(); m_picking_node_pbo->request_read(pickbufferpos.x, pickbufferpos.y, 1, 1); m_pick_fb->unbind(); diff --git a/renderer.h b/renderer.h index efe194bb..74027361 100644 --- a/renderer.h +++ b/renderer.h @@ -235,7 +235,12 @@ class opengl_renderer float draw_range{0.0f}; }; - struct viewport_data { + struct viewport_config { + int width; + int height; + + glm::mat4 camera_transform; + std::unique_ptr msaa_fb; std::unique_ptr msaa_rbc; std::unique_ptr msaa_rbv; @@ -264,16 +269,16 @@ class opengl_renderer // methods std::unique_ptr make_shader(std::string v, std::string f); bool Init_caps(); - void setup_pass(renderpass_config &Config, rendermode const Mode, float const Znear = 0.f, float const Zfar = 1.f, bool const Ignoredebug = false); + void setup_pass(viewport_config &Viewport, renderpass_config &Config, rendermode const Mode, float const Znear = 0.f, float const Zfar = 1.f, bool const Ignoredebug = false); void setup_matrices(); void setup_drawing(bool const Alpha = false); void setup_shadow_map(opengl_texture *tex, renderpass_config conf); void setup_env_map(gl::cubemap *tex); void setup_environment_light(TEnvironmentType const Environment = e_flat); // runs jobs needed to generate graphics for specified render pass - void Render_pass(viewport_data &vp, rendermode const Mode); + void Render_pass(viewport_config &vp, rendermode const Mode); // creates dynamic environment cubemap - bool Render_reflections(viewport_data &vp); + bool Render_reflections(viewport_config &vp); bool Render(world_environment *Environment); void Render(scene::basic_region *Region); void Render(section_sequence::iterator First, section_sequence::iterator Last); @@ -302,7 +307,7 @@ class opengl_renderer glm::vec3 pick_color(std::size_t const Index); std::size_t pick_index(glm::ivec3 const &Color); - bool init_viewport(viewport_data &vp); + bool init_viewport(viewport_config &vp); void draw(const gfx::geometry_handle &handle); void draw(std::vector::iterator begin, std::vector::iterator end); @@ -350,7 +355,7 @@ class opengl_renderer float m_fogrange = 2000.0f; renderpass_config m_renderpass; // parameters for current render pass - viewport_data *m_current_viewport; // active viewport + viewport_config *m_current_viewport; // active viewport section_sequence m_sectionqueue; // list of sections in current render pass cell_sequence m_cellqueue; renderpass_config m_colorpass; // parametrs of most recent color pass @@ -395,7 +400,7 @@ class opengl_renderer std::unique_ptr m_empty_vao; - viewport_data default_viewport; + std::vector> m_viewports; std::unique_ptr m_pfx_motionblur; std::unique_ptr m_pfx_tonemapping; diff --git a/scenarioloadermode.cpp b/scenarioloadermode.cpp index 9ec486a6..15e2b443 100644 --- a/scenarioloadermode.cpp +++ b/scenarioloadermode.cpp @@ -91,6 +91,6 @@ scenarioloader_mode::enter() { void scenarioloader_mode::exit() { - simulation::Time.init(); + simulation::Time.init(Global.starting_timestamp); simulation::Environment.init(); } diff --git a/simulation.cpp b/simulation.cpp index 6629f99c..c712bbe6 100644 --- a/simulation.cpp +++ b/simulation.cpp @@ -130,7 +130,7 @@ void state_manager::process_commands() { if (commanddata.command == user_command::queueevent) { uint32_t id = std::round(commanddata.param1); - basic_event *ev = Events.FindEventById(id); + basic_event *ev = Events.FindEventById(id); // TODO: depends on vector position Events.AddToQuery(ev, nullptr); } @@ -139,7 +139,26 @@ void state_manager::process_commands() { int light = std::round(commanddata.param1); float state = commanddata.param2; if (id < simulation::Instances.sequence().size()) - simulation::Instances.sequence()[id]->LightSet(light, state); + simulation::Instances.sequence()[id]->LightSet(light, state); // TODO: depends on vector position + } + + if (commanddata.command == user_command::setdatetime) { + int yearday = std::round(commanddata.param1); + int minute = std::round(commanddata.param2 * 60.0); + simulation::Time.set_time(yearday, minute); + + simulation::Environment.compute_season(yearday); + } + + if (commanddata.command == user_command::setweather) { + Global.fFogEnd = commanddata.param1; + Global.Overcast = commanddata.param2; + + simulation::Environment.compute_weather(); + } + + if (commanddata.command == user_command::settemperature) { + Global.AirTemperature = commanddata.param1; } if (DebugModeFlag) { diff --git a/simulationenvironment.cpp b/simulationenvironment.cpp index 2bc27b25..69545dc1 100644 --- a/simulationenvironment.cpp +++ b/simulationenvironment.cpp @@ -35,7 +35,7 @@ world_environment::toggle_daylight() { // calculates current season of the year based on set simulation date void -world_environment::compute_season( int const Yearday ) const { +world_environment::compute_season( int const Yearday ) { using dayseasonpair = std::pair; @@ -59,7 +59,7 @@ world_environment::compute_season( int const Yearday ) const { // calculates current weather void -world_environment::compute_weather() const { +world_environment::compute_weather() { Global.Weather = ( Global.Overcast <= 0.25 ? "clear:" : @@ -67,6 +67,8 @@ world_environment::compute_weather() const { ( Global.Season != "winter:" ? "rain:" : "snow:" ) ); + + m_precipitation.update_weather(); } void @@ -161,7 +163,11 @@ world_environment::update() { else if( Global.Weather == "snow:" ) { // reduce friction due to snow Global.FrictionWeatherFactor = 0.75f; + m_precipitationsound.stop(); } + else { + m_precipitationsound.stop(); + } } void diff --git a/simulationenvironment.h b/simulationenvironment.h index 2f69f631..55ed0406 100644 --- a/simulationenvironment.h +++ b/simulationenvironment.h @@ -33,9 +33,9 @@ public: // switches between static and dynamic daylight calculation void toggle_daylight(); // calculates current season of the year based on set simulation date - void compute_season( int const Yearday ) const; + void compute_season( int const Yearday ); // calculates current weather - void compute_weather() const; + void compute_weather(); private: // members diff --git a/simulationstateserializer.cpp b/simulationstateserializer.cpp index 6d2679ae..d68f2627 100644 --- a/simulationstateserializer.cpp +++ b/simulationstateserializer.cpp @@ -150,10 +150,9 @@ state_serializer::deserialize_atmo( cParser &Input, scene::scratch_data &Scratch // atmosphere color; legacy parameter, no longer used Input.getTokens( 3 ); // fog range - Input.getTokens( 2 ); - Input - >> Global.fFogStart - >> Global.fFogEnd; + Input.getTokens( 1 ); // fog start ignored + Input.getTokens( 1 ); + Input >> Global.fFogEnd; if( Global.fFogEnd > 0.0 ) { // fog colour; optional legacy parameter, no longer used @@ -297,9 +296,24 @@ state_serializer::deserialize_firstinit( cParser &Input, scene::scratch_data &Sc simulation::Events.InitLaunchers(); simulation::Memory.InitCells(); + init_time(); + Scratchpad.initialized = true; } +void state_serializer::init_time() { + auto &time = simulation::Time.data(); + if( true == Global.ScenarioTimeCurrent ) { + // calculate time shift required to match scenario time with local clock + auto const *localtime = std::gmtime( &Global.starting_timestamp ); + Global.ScenarioTimeOffset = ( ( localtime->tm_hour * 60 + localtime->tm_min ) - ( time.wHour * 60 + time.wMinute ) ) / 60.f; + } + else if( false == std::isnan( Global.ScenarioTimeOverride ) ) { + // scenario time override takes precedence over scenario time offset + Global.ScenarioTimeOffset = ( ( Global.ScenarioTimeOverride * 60 ) - ( time.wHour * 60 + time.wMinute ) ) / 60.f; + } +} + void state_serializer::deserialize_group( cParser &Input, scene::scratch_data &Scratchpad ) { @@ -583,17 +597,6 @@ state_serializer::deserialize_time( cParser &Input, scene::scratch_data &Scratch >> time.wHour >> time.wMinute; - if( true == Global.ScenarioTimeCurrent ) { - // calculate time shift required to match scenario time with local clock - auto timenow = std::time( 0 ); - auto const *localtime = std::localtime( &timenow ); - Global.ScenarioTimeOffset = ( ( localtime->tm_hour * 60 + localtime->tm_min ) - ( time.wHour * 60 + time.wMinute ) ) / 60.f; - } - else if( false == std::isnan( Global.ScenarioTimeOverride ) ) { - // scenario time override takes precedence over scenario time offset - Global.ScenarioTimeOffset = ( ( Global.ScenarioTimeOverride * 60 ) - ( time.wHour * 60 + time.wMinute ) ) / 60.f; - } - // remaining sunrise and sunset parameters are no longer used, as they're now calculated dynamically // anything else left in the section has no defined meaning skip_until( Input, "endtime" ); diff --git a/simulationstateserializer.h b/simulationstateserializer.h index 6790b129..619ae0f7 100644 --- a/simulationstateserializer.h +++ b/simulationstateserializer.h @@ -73,6 +73,7 @@ private: TAnimModel * deserialize_model( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); TDynamicObject * deserialize_dynamic( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); sound_source * deserialize_sound( cParser &Input, scene::scratch_data &Scratchpad, scene::node_data const &Nodedata ); + void init_time(); // skips content of stream until specified token void skip_until( cParser &Input, std::string const &Token ); // transforms provided location by specifed rotation and offset diff --git a/simulationtime.cpp b/simulationtime.cpp index 6be76b42..3d2e72e4 100644 --- a/simulationtime.cpp +++ b/simulationtime.cpp @@ -20,7 +20,7 @@ scenario_time Time; } // simulation void -scenario_time::init() { +scenario_time::init(std::time_t timestamp) { char monthdaycounts[ 2 ][ 13 ] = { { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 0, 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } }; @@ -32,10 +32,7 @@ scenario_time::init() { auto const requestedminute { requestedtime % 60 }; // cache requested elements, if any -#ifdef __linux__ - timespec ts; - clock_gettime(CLOCK_REALTIME, &ts); - tm *tms = localtime(&ts.tv_sec); + std::tm *tms = std::gmtime(×tamp); m_time.wYear = tms->tm_year; m_time.wMonth = tms->tm_mon; m_time.wDayOfWeek = tms->tm_wday; @@ -43,16 +40,7 @@ scenario_time::init() { m_time.wHour = tms->tm_hour; m_time.wMinute = tms->tm_min; m_time.wSecond = tms->tm_sec; - m_time.wMilliseconds = ts.tv_nsec / 1000000; - -/* - time_t local = mktime(localtime(&ts.tv_sec)); - time_t utc = mktime(gmtime(&ts.tv_sec)); - m_timezonebias = (double)(local - utc) / 3600.0f; -*/ -#elif _WIN32 - ::GetLocalTime( &m_time ); -#endif + m_time.wMilliseconds = 0; if( Global.fMoveLight > 0.0 ) { // day and month of the year can be overriden by scenario setup @@ -94,7 +82,7 @@ scenario_time::init() { zonebias += timezoneinfo.StandardBias; } - m_timezonebias = ( zonebias / 60.0 ); + m_timezonebias = ( zonebias / 60.0 ); } void @@ -197,6 +185,12 @@ scenario_time::julian_day() const { return JD; } +void scenario_time::set_time(int yearday, int minute) { + daymonth(m_time.wDay, m_time.wMonth, m_time.wYear, yearday); + m_time.wHour = minute / 60; + m_time.wMinute = minute % 60; +} + // calculates day of week for provided date int scenario_time::day_of_week( int const Day, int const Month, int const Year ) const { diff --git a/simulationtime.h b/simulationtime.h index e5667360..436a0948 100644 --- a/simulationtime.h +++ b/simulationtime.h @@ -20,7 +20,7 @@ public: scenario_time() { m_time.wHour = 10; m_time.wMinute = 30; } void - init(); + init(time_t timestamp); void update( double const Deltatime ); inline @@ -45,6 +45,8 @@ public: double zone_bias() const { return m_timezonebias; } + void + set_time(int yearday, int minute); /** Returns std::string in format: `"mm:ss"`. */ operator std::string(); diff --git a/sn_utils.cpp b/sn_utils.cpp index 3831d9c5..d96dc74a 100644 --- a/sn_utils.cpp +++ b/sn_utils.cpp @@ -38,6 +38,18 @@ int32_t sn_utils::ld_int32(std::istream &s) return reinterpret_cast(v); } +// deserialize little endian int64 +int64_t sn_utils::ld_int64(std::istream &s) +{ + uint8_t buf[8]; + s.read((char*)buf, 8); + uint64_t v = ((uint64_t)buf[7] << 56) | ((uint64_t)buf[6] << 48) | + ((uint64_t)buf[5] << 40) | ((uint64_t)buf[4] << 32) | + ((uint64_t)buf[3] << 24) | ((uint64_t)buf[2] << 16) | + ((uint64_t)buf[1] << 8) | (uint64_t)buf[0]; + return reinterpret_cast(v); +} + // deserialize little endian ieee754 float32 float sn_utils::ld_float32(std::istream &s) { @@ -133,6 +145,20 @@ void sn_utils::ls_int32(std::ostream &s, int32_t v) s.write((char*)buf, 4); } +void sn_utils::ls_int64(std::ostream &s, int64_t v) +{ + uint8_t buf[8]; + buf[0] = v; + buf[1] = v >> 8; + buf[2] = v >> 16; + buf[3] = v >> 24; + buf[4] = v >> 32; + buf[5] = v >> 40; + buf[6] = v >> 48; + buf[7] = v >> 56; + s.write((char*)buf, 8); +} + void sn_utils::ls_float32(std::ostream &s, float t) { uint32_t v = reinterpret_cast(t); diff --git a/sn_utils.h b/sn_utils.h index 2fd2d7df..fff3125a 100644 --- a/sn_utils.h +++ b/sn_utils.h @@ -10,6 +10,7 @@ public: static uint16_t ld_uint16(std::istream&); static uint32_t ld_uint32(std::istream&); static int32_t ld_int32(std::istream&); + static int64_t ld_int64(std::istream&); static float ld_float32(std::istream&); static double ld_float64(std::istream&); static std::string d_str(std::istream&); @@ -21,6 +22,7 @@ public: static void ls_uint16(std::ostream&, uint16_t); static void ls_uint32(std::ostream&, uint32_t); static void ls_int32(std::ostream&, int32_t); + static void ls_int64(std::ostream&, int64_t); static void ls_float32(std::ostream&, float); static void ls_float64(std::ostream&, double); static void s_str(std::ostream&, std::string); diff --git a/translation.cpp b/translation.cpp index 1a68bc75..59fa0bf3 100644 --- a/translation.cpp +++ b/translation.cpp @@ -79,6 +79,14 @@ init() { "Map", "Mode windows", + "Time and environment", + "Time", + "Day in year", + "Visibility", + "Overcast and precipitation", + "Temperature", + "Apply", + "Straight |", "Divert /", @@ -242,6 +250,14 @@ init() { u8"Mapa", u8"Okna trybu", + u8"Czas i środowisko", + u8"Czas", + u8"Dzień w roku", + u8"Widoczność", + u8"Zachmurzenie i opady", + u8"Temperatura", + u8"Zastosuj", + u8"Prosto |", u8"W bok /", diff --git a/translation.h b/translation.h index 882179aa..17723a2b 100644 --- a/translation.h +++ b/translation.h @@ -68,6 +68,14 @@ enum string { ui_map, ui_mode_windows, + time_window, + time_time, + time_yearday, + time_visibility, + time_weather, + time_temperature, + time_apply, + map_straight, map_divert, diff --git a/widgets/map.cpp b/widgets/map.cpp index 170270b7..4efa58d3 100644 --- a/widgets/map.cpp +++ b/widgets/map.cpp @@ -133,8 +133,10 @@ void ui::map_panel::render_labels(glm::mat4 transform, ImVec2 origin, glm::vec2 for (TDynamicObject *vehicle : simulation::Vehicles.sequence()) { if (vehicle->Prev() || !vehicle->Mechanik) continue; - if (vehicle->Mechanik->TrainName().empty()) - continue; + + std::string label = vehicle->Mechanik->TrainName(); + if (label.empty() || label == "none") + label = ToUpper(vehicle->name()); glm::vec4 ndc_pos = transform * glm::vec4(glm::vec3(vehicle->GetPosition()), 1.0f); if (glm::abs(ndc_pos.x) > 1.0f || glm::abs(ndc_pos.z) > 1.0f) @@ -144,11 +146,10 @@ void ui::map_panel::render_labels(glm::mat4 transform, ImVec2 origin, glm::vec2 TDynamicObject *veh = vehicle; - const char *desc = vehicle->Mechanik->TrainName().c_str(); - ImVec2 textsize = ImGui::CalcTextSize(desc); + ImVec2 textsize = ImGui::CalcTextSize(label.c_str()); ImGui::SetCursorPos(ImVec2(origin.x + gui_pos.x - textsize.x / 2.0f, origin.y + gui_pos.y - textsize.y / 2.0f)); - ImGui::TextUnformatted(desc); + ImGui::TextUnformatted(label.c_str()); } ImGui::PopStyleColor(); diff --git a/widgets/time.cpp b/widgets/time.cpp new file mode 100644 index 00000000..67d5f270 --- /dev/null +++ b/widgets/time.cpp @@ -0,0 +1,41 @@ +#include "stdafx.h" +#include "widgets/time.h" +#include "simulationtime.h" +#include "Globals.h" + +ui::time_panel::time_panel() + : ui_panel(LOC_STR(time_window), false) +{ + size.x = 450; +} + +void ui::time_panel::render_contents() +{ + ImGui::SliderFloat(LOC_STR(time_time), &time, 0.0f, 24.0f, "%.1f"); + ImGui::SliderInt(LOC_STR(time_yearday), &yearday, 1, 365); + ImGui::SliderFloat(LOC_STR(time_visibility), &fog, 50.0f, 3000.0f, "%.0f"); + ImGui::SliderFloat(LOC_STR(time_weather), &overcast, 0.0f, 2.0f, "%.1f"); + ImGui::SliderFloat(LOC_STR(time_temperature), &temperature, -20.0f, 40.0f, "%.0f"); + + if (ImGui::Button(LOC_STR(time_apply))) { + m_relay.post(user_command::setdatetime, (double)yearday, time, 1, 0); + m_relay.post(user_command::setweather, fog, overcast, 1, 0); + m_relay.post(user_command::settemperature, temperature, 0.0, 1, 0); + + is_open = false; + } +} + +void ui::time_panel::open() +{ + auto &data = simulation::Time.data(); + time = (float)data.wHour + (float)data.wMinute / 60.0f; + + yearday = simulation::Time.year_day(); + fog = Global.fFogEnd; + + overcast = Global.Overcast; + temperature = Global.AirTemperature; + + is_open = true; +} diff --git a/widgets/time.h b/widgets/time.h new file mode 100644 index 00000000..e5788d66 --- /dev/null +++ b/widgets/time.h @@ -0,0 +1,21 @@ +#include "uilayer.h" +#include "translation.h" +#include "command.h" + +namespace ui { + class time_panel : public ui_panel { + command_relay m_relay; + + float time; + int yearday; + float fog; + float overcast; + float temperature; + + public: + time_panel(); + + void render_contents() override; + void open(); + }; +}