From ca8d807601eb2a9396cacd0b49d4a4f364b08905 Mon Sep 17 00:00:00 2001 From: ineveraskedforthis Date: Sat, 8 Aug 2026 12:43:44 +0300 Subject: [PATCH 1/4] improve roads generation, add "gores"-based graphics mode, make animation of goods moving around the world more fun --- assets/images/cargo-ship.png | Bin 0 -> 4062 bytes assets/images/train.png | Bin 0 -> 5219 bytes assets/localisation/en-US/settings.csv | 1 + assets/shaders/glsl/map_f.glsl | 152 ++++++-- assets/shaders/glsl/map_font_v.glsl | 2 +- assets/shaders/glsl/wavy_textured_line_f.glsl | 20 +- src/common_types/constants.hpp | 2 +- src/gamestate/serialization.cpp | 9 + src/gamestate/system_state.cpp | 347 +++++++++++++++++- src/gui/main_menu.cpp | 1 + src/map/map.cpp | 211 +++++++++-- src/map/map.hpp | 11 +- 12 files changed, 697 insertions(+), 59 deletions(-) create mode 100644 assets/images/cargo-ship.png create mode 100644 assets/images/train.png diff --git a/assets/images/cargo-ship.png b/assets/images/cargo-ship.png new file mode 100644 index 0000000000000000000000000000000000000000..b103cbdb01b2badd50d6a22636d96671c2d58e0c GIT binary patch literal 4062 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 QeEokUHbhi#L{X8Z2r?+( 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 literal 0 HcmV?d00001 diff --git a/assets/localisation/en-US/settings.csv b/assets/localisation/en-US/settings.csv index c0d5fb25ac..0237a8d751 100644 --- a/assets/localisation/en-US/settings.csv +++ b/assets/localisation/en-US/settings.csv @@ -60,4 +60,5 @@ graphics_details_label;Graphics graphics_details_0;Ugly graphics_details_1;Classic graphics_details_2;Modern +graphics_details_3;Gores disband_confirmation_label;Unit disband confirmation diff --git a/assets/shaders/glsl/map_f.glsl b/assets/shaders/glsl/map_f.glsl index 4c5802de36..f3cae42dfa 100644 --- a/assets/shaders/glsl/map_f.glsl +++ b/assets/shaders/glsl/map_f.glsl @@ -189,13 +189,34 @@ vec4 get_land_terrain() { return get_terrain_mix(); } +float get_good_hatching(float frequency) { + float s = space_coords.z; + float t = space_coords.y; + float h = space_coords.x; + float angle = atan(t, s); + float vertical_angle = asin(h); + float radius = sqrt(s * s + t * t); + float equator_segment_x = fract(angle * 36.f / 2.f / PI); + float equator_segment_number = floor(angle * 36.f / 2.f / PI); + float vertical_segment = fract(vertical_angle * 2.f / PI * 8.f); + float vertical_segment_number = floor(vertical_angle * 2.f / PI * 8.f); + float horisontal_coord_before_cutting_paper = (equator_segment_x - 0.5f) * radius + 0.5f; + float hatching = texture(hatching, vec2(horisontal_coord_before_cutting_paper * (mod(vertical_segment_number, 2.f) * 2.f - 1.f), vertical_segment * (mod(equator_segment_number, 2.f) * 2.f - 1.f)) * frequency).r; + return hatching; +} + // if we display data, ignore pretty effects vec4 get_land_data_modern() { vec4 terrain = get_terrain_mix(); float is_land = terrain.a; vec4 province_sample = texture(provinces_texture_sampler, gl_FragCoord.xy / screen_size); vec2 prov_id = province_sample.xy; - return vec4(texture(province_color, vec3(prov_id, 0.)).rgb * 0.8f, is_land); + + vec4 base_color = texture(province_color, vec3(prov_id, 0.)); + vec4 secondary_color = texture(province_color, vec3(prov_id, 1.)); + vec4 result = mix(base_color, secondary_color, get_good_hatching(10.f)); + + return vec4(result.rgb * 0.8f, is_land); } vec4 get_land_political_modern() { @@ -237,38 +258,76 @@ vec4 get_land_political_modern() { return OutColor; } +vec4 get_land_political_gores() { + float s = space_coords.z; + float t = space_coords.y; -vec4 get_land_political_paper() { - vec2 uv = tex_coord * 300.f; + float h = space_coords.x; + + float angle = atan(t, s); + + float vertical_angle = asin(h); + + float radius = sqrt(s * s + t * t); + float equator_segment_x = fract(angle * 36.f / 2.f / PI); + float equator_segment_number = floor(angle * 36.f / 2.f / PI); + float vertical_segment = fract(vertical_angle * 2.f / PI * 8.f); + float vertical_segment_number = floor(vertical_angle * 2.f / PI * 8.f); + + float horisontal_coord_before_cutting_paper = (equator_segment_x - 0.5f) * radius + 0.5f; + + float line_width = 0.0075f; + + float v1 = max(0, line_width - equator_segment_x * (1.f - equator_segment_x)); + float v2 = max(0.f, line_width - vertical_segment * (1.f - vertical_segment)); + float line_mult = 1.f - (v1 + v2) * (v1 + v2) * 10000.f; + + vec4 wc = texture(watercolor, vec2(horisontal_coord_before_cutting_paper, vertical_segment) * 10.f); + vec4 wc_global = texture(watercolor, vec2(horisontal_coord_before_cutting_paper, vertical_segment) / 2.f); + + float hatching = texture(hatching, vec2(horisontal_coord_before_cutting_paper * (mod(vertical_segment_number, 2.f) * 2.f - 1.f), vertical_segment * (mod(equator_segment_number, 2.f) * 2.f - 1.f)) * 20.f).r; + + vec4 terrain = get_terrain_mix(); + float is_land = terrain.a; vec4 province_sample = texture(provinces_texture_sampler, gl_FragCoord.xy / screen_size); vec2 prov_id = province_sample.xy; float to_national_border = province_sample.z; - vec4 hatching = texture(hatching, uv / 2.f); - vec4 prov_highlight = vec4(1.f, 1.f, 1.f, 1.f); float highlight = texture(province_highlight, prov_id).r; - if (highlight > 0.f) { - prov_highlight = vec4(hatching.rgb * 0.2f * (hatching.a) + 0.5f * (1.f - hatching.a), 1.f); + prov_highlight = vec4(hatching, hatching, hatching, 1.f); } - vec4 prov_color = texture(province_color, vec3(prov_id, 0.)); - - vec4 base_color = vec4(1.f, 247.f / 255.f, 240.f / 255.f, 1.f); - float wc_base = texture(watercolor, uv).b; - float gradient = 1.f; - if (int(graphics_mode) == 2) { - gradient = (0.7f + sqrt(to_national_border) * 0.3f); - } - float wc_texture = max(0.f, wc_base * wc_base * wc_base) * gradient; - vec4 mixed_color = prov_color * (1.f - wc_texture) + base_color * wc_texture; - - vec4 terrain = get_terrain_mix(); + //vec4 base_color = vec4(1.f, 247.f / 255.f, 220.f / 255.f, 1.f) * 0.9f; + vec4 base_color = vec4(0.93, 0.90, 0.83, 1.f); + float gradient = sqrt(to_national_border); + const vec3 GREYIFY = vec3( 0.212671, 0.715160, 0.072169 ); float grey = dot( terrain.rgb, GREYIFY ) * 2.f; - float is_land = terrain.a; - return mixed_color * prov_highlight * is_land + texture(watercolor, uv) * gradient * (1 - is_land); + + float weight_base = 0.0015f; + float weight_hatching = 0.05f; + float weight_border = 3.0f * clamp(0.1f + hatching, 0.f, 1.f); + float weight_watercolor = 0.005f; + //float total_weight = weight_base + weight_hatching + weight_border + weight_watercolor; + float wc_g = dot(wc.xyz, GREYIFY); + + float final_weight = min(1, sqrt( + weight_base + + wc_g * weight_watercolor + + weight_border * (1.f - gradient) + + hatching * weight_hatching + )); + + vec4 mixed_color = prov_color * final_weight + base_color * (1.f - final_weight); + + //vec4 color = prov_color * wc + + vec4 result = (mixed_color - 0.f * hatching) * min(1.f, 0.3f + line_mult * line_mult) * (0.85f + 0.05f * wc + 0.10f * dot(wc_global.xyz, GREYIFY)); + result.a = is_land; + + return result; } vec4 get_land_political_close() { @@ -319,6 +378,7 @@ vec4 get_land_political_close() { return terrain; } + vec4 get_land_political_far() { //vec4 terrain = get_terrain(vec2(0, 0), vec2(0)); vec4 terrain = get_terrain_mix(); @@ -386,19 +446,28 @@ vec4 get_land() { return get_land_terrain(); } + if (int(graphics_mode) == 2) { if (map_mode_is_data == 0) { return get_land_political_modern(); } else { return get_land_data_modern(); } - return get_land_political_far(); + } + + if (int(graphics_mode) == 3) { + if (map_mode_is_data == 0) { + return get_land_political_gores(); + } else { + return get_land_data_modern(); + } } if (int(graphics_mode) == 0) { return get_land_political_far(); } + switch(int(subroutines_index_2)) { case 0: return get_land_terrain(); case 1: return get_land_political_close(); @@ -406,7 +475,44 @@ vec4 get_land() { default: break; } } + +vec4 get_water_gores() { + float s = space_coords.z; + float t = space_coords.y; + + float h = space_coords.x; + + float angle = atan(t, s); + + float vertical_angle = asin(h); + + float radius = sqrt(s * s + t * t); + + float equator_segment_x = fract(angle * 36.f / 2.f / PI); + float equator_segment_number = floor(angle * 36.f / 2.f / PI); + float vertical_segment = fract(vertical_angle * 2.f / PI * 8.f); + float vertical_segment_number = floor(vertical_angle * 2.f / PI * 8.f); + + float horisontal_coord_before_cutting_paper = (equator_segment_x - 0.5f) * radius + 0.5f; + + float line_width = 0.0075f; + + float v1 = max(0, line_width - equator_segment_x * (1.f - equator_segment_x)); + float v2 = max(0.f, line_width - vertical_segment * (1.f - vertical_segment)); + float line_mult = 1.f - (v1 + v2) * (v1 + v2) * 10000.f; + + vec4 wc = texture(watercolor, vec2(horisontal_coord_before_cutting_paper, vertical_segment) * 10.f); + vec4 wc_global = texture(watercolor, vec2(horisontal_coord_before_cutting_paper, vertical_segment) / 2.f); + + float hatching = 0.2f * texture(hatching, vec2(horisontal_coord_before_cutting_paper * (mod(vertical_segment_number, 2.f) * 2.f - 1.f), vertical_segment * (mod(equator_segment_number, 2.f) * 2.f - 1.f)) * 20.f).r; + return vec4(0.77f - hatching, 0.85f - hatching, 0.80f, 1.f) * (0.25f + 0.5f * wc + 0.25f * wc_global) * min(1.f, line_mult * line_mult); +} + vec4 get_water() { + if (int(graphics_mode) == 3) { + return get_water_gores(); + } + switch(int(subroutines_index_2)) { case 0: return get_water_terrain(); case 1: return get_water_terrain(); @@ -429,7 +535,7 @@ void main() { float light = max(0.f, dot(light_direction, space_coords)); float darkness = max(0.f, -dot(light_direction, space_coords)); - if (int(graphics_mode) == 2) { + if (int(graphics_mode) == 2 || int(graphics_mode) == 3) { vec4 province_sample = texture(provinces_texture_sampler, gl_FragCoord.xy / screen_size); float to_national_border = province_sample.z; //light += (1.f - to_national_border) * 1.5f * darkness; diff --git a/assets/shaders/glsl/map_font_v.glsl b/assets/shaders/glsl/map_font_v.glsl index 164c30e98a..ce4d4805f5 100644 --- a/assets/shaders/glsl/map_font_v.glsl +++ b/assets/shaders/glsl/map_font_v.glsl @@ -25,7 +25,7 @@ void main() { vec4 temp_result = calc_gl_position(vertex_position); float pixels_thickness = thickness * zoom; - float min_thickness = 0.01f; + float min_thickness = 0.003f; float max_thickness = 1.f; opacity = clamp((min_thickness / pixels_thickness - 1.f) * (pixels_thickness / max_thickness - 1.f), 0.f, 1.f); diff --git a/assets/shaders/glsl/wavy_textured_line_f.glsl b/assets/shaders/glsl/wavy_textured_line_f.glsl index 4d639e29cd..0297e8539c 100644 --- a/assets/shaders/glsl/wavy_textured_line_f.glsl +++ b/assets/shaders/glsl/wavy_textured_line_f.glsl @@ -9,6 +9,7 @@ uniform vec3 light_direction; uniform float ignore_light; uniform float gamma; uniform vec2 screen_size; +uniform float railroad_level; uniform sampler2D provinces_texture_sampler; uniform sampler2D province_fow; @@ -20,10 +21,24 @@ vec4 gamma_correct(vec4 colour) { } void main() { + + float value = abs(0.5f - tex_coord); + vec2 prov_id = texture(provinces_texture_sampler, gl_FragCoord.xy / screen_size).xy; + + if (ignore_light > 0.f) { + if (texture(provinces_sea_mask, prov_id).x > 0) { + frag_color = vec4(0.0f, 0.0f, 0.0f, sin(o_dist * 2.f)); + } else { + float rail = max(0.f, sin(railroad_level * o_dist * 2.f)); + rail = rail * rail; + frag_color = vec4(0.0f, 0.0f, 0.0f, (value) * (value) * 4.f + rail); + } + return ; + } + float darkness = max(0.f, -dot(light_direction, space_coords) + 0.1f); float texture_mask = texture(line_texture, vec2(o_dist, tex_coord)).r; - vec2 prov_id = texture(provinces_texture_sampler, gl_FragCoord.xy / screen_size).xy; if (texture(provinces_sea_mask, prov_id).x > 0 || (prov_id.x == 0.f && prov_id.y == 0.f)) { discard; } @@ -33,7 +48,8 @@ void main() { float dotted_line = max(0.f, sin(o_dist * 10.f) - 0.5f); //float value = abs( (0.5f + sin(o_dist * 1.f) * 0.2) - tex_coord); - float value = abs(0.5f - tex_coord); + + vec3 inner_color = vec3(0.8f, 0.2, 0.2f); if (ignore_light == 0.f) { diff --git a/src/common_types/constants.hpp b/src/common_types/constants.hpp index 57e9416184..b6f6f8149a 100644 --- a/src/common_types/constants.hpp +++ b/src/common_types/constants.hpp @@ -578,7 +578,7 @@ enum class color_blind_mode { }; enum class graphics_mode { - ugly, classic, modern, total + ugly, classic, modern, modern_classic, total }; diff --git a/src/gamestate/serialization.cpp b/src/gamestate/serialization.cpp index 326bafc441..93d8c4ad0b 100644 --- a/src/gamestate/serialization.cpp +++ b/src/gamestate/serialization.cpp @@ -161,6 +161,9 @@ uint8_t const* read_handwritten_scenario_section(uint8_t const* ptr_in, uint8_t ptr_in = deserialize(ptr_in, state.map_state.map_data.coastal_vertices); ptr_in = deserialize(ptr_in, state.map_state.map_data.coastal_starts); ptr_in = deserialize(ptr_in, state.map_state.map_data.coastal_counts); + ptr_in = deserialize(ptr_in, state.map_state.map_data.railroad_vertices); + ptr_in = deserialize(ptr_in, state.map_state.map_data.railroad_starts); + ptr_in = deserialize(ptr_in, state.map_state.map_data.railroad_counts); ptr_in = deserialize(ptr_in, state.map_state.map_data.province_border_vertices); ptr_in = deserialize(ptr_in, state.map_state.map_data.province_border_starts); ptr_in = deserialize(ptr_in, state.map_state.map_data.province_border_counts); @@ -373,6 +376,9 @@ uint8_t* write_handwritten_scenario_section(uint8_t* ptr_in, sys::state& state, ptr_in = serialize(ptr_in, state.map_state.map_data.coastal_vertices); ptr_in = serialize(ptr_in, state.map_state.map_data.coastal_starts); ptr_in = serialize(ptr_in, state.map_state.map_data.coastal_counts); + ptr_in = serialize(ptr_in, state.map_state.map_data.railroad_vertices); + ptr_in = serialize(ptr_in, state.map_state.map_data.railroad_starts); + ptr_in = serialize(ptr_in, state.map_state.map_data.railroad_counts); ptr_in = serialize(ptr_in, state.map_state.map_data.province_border_vertices); ptr_in = serialize(ptr_in, state.map_state.map_data.province_border_starts); ptr_in = serialize(ptr_in, state.map_state.map_data.province_border_counts); @@ -585,6 +591,9 @@ size_t sizeof_handwritten_scenario_section(sys::state& state, bool exclude_local sz += serialize_size(state.map_state.map_data.coastal_vertices); sz += serialize_size(state.map_state.map_data.coastal_starts); sz += serialize_size(state.map_state.map_data.coastal_counts); + sz += serialize_size(state.map_state.map_data.railroad_vertices); + sz += serialize_size(state.map_state.map_data.railroad_starts); + sz += serialize_size(state.map_state.map_data.railroad_counts); sz += serialize_size(state.map_state.map_data.province_border_vertices); sz += serialize_size(state.map_state.map_data.province_border_starts); sz += serialize_size(state.map_state.map_data.province_border_counts); diff --git a/src/gamestate/system_state.cpp b/src/gamestate/system_state.cpp index ad8ab5652e..95a99cd37d 100644 --- a/src/gamestate/system_state.cpp +++ b/src/gamestate/system_state.cpp @@ -37,6 +37,7 @@ #include "alice_ui.hpp" #include "commands.hpp" #include "dcon_oos_reporter_generated.hpp" +#include "math_fns.hpp" namespace sys { @@ -50,6 +51,17 @@ void state::start_state_selection(state_selection_data& data) { } } +glm::vec2 put_in_local(glm::vec2 new_point, glm::vec2 base_point, float size_x) { + auto uadjx = std::abs(new_point.x - base_point.x); + auto ladjx = std::abs(new_point.x - size_x - base_point.x); + auto radjx = std::abs(new_point.x + size_x - base_point.x); + if(uadjx < ladjx) { + return uadjx < radjx ? new_point : glm::vec2{ new_point.x + size_x, new_point.y }; + } else { + return ladjx < radjx ? glm::vec2{ new_point.x - size_x, new_point.y } : glm::vec2{ new_point.x + size_x, new_point.y }; + } +} + void state::start_national_identity_selection(national_identity_selection_data& data) { national_identity_selection = data; @@ -1863,6 +1875,22 @@ void state::open_diplomacy(dcon::nation_id target) { sys::open_diplomacy_window(*this, target); } +struct pixel_heap_member { + int x; + int y; + float dist; + float heur; + + int8_t dx; + int8_t dy; + uint8_t speed; + + auto operator<=>(const pixel_heap_member & other) const { + return (dist + heur) <=> (other.dist + other.heur); + } +}; + + void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day bookmark_date) { auto root = get_root(common_fs); auto common = open_directory(root, NATIVE("common")); @@ -3654,6 +3682,323 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day military::set_initial_leaders(*this); + province::restore_distances(*this); + + // generate a road for every adjacency + + assert(_CrtCheckMemory()); + + std::vector visited; + visited.resize(map_state.map_data.size_x * map_state.map_data.size_y); + + int left_dirty = map_state.map_data.size_x * map_state.map_data.size_y; + int right_dirty = 0; + + auto encode_ternary = [&](int8_t flag, int8_t dx, int8_t dy) -> int8_t { + return flag * 9 + dx * 3 + dy; + }; + + auto set_visited = [&](int idx, int8_t movement) { + visited[idx] = movement; + left_dirty = std::min(left_dirty, idx); + right_dirty = std::max(right_dirty, idx); + }; + + auto clear_visited = [&] () { + memset(&visited[left_dirty], 0, right_dirty - left_dirty * (sizeof(uint8_t))); + }; + + std::vector pixel_queue{ }; + + + std::vector terrain_cost{}; + terrain_cost.resize(256); + terrain_cost[255] = 15.f; + + auto max_cost = 0.f; + for(int terrain = 0; terrain < 64; terrain++) { + auto modifier = context.modifier_by_terrain_index[terrain]; + if(modifier) { + auto& modifier_data = world.modifier_get_province_values(modifier); + auto move_cost_mod = 0.f; + for(uint32_t mod_index = 0; mod_index < sys::provincial_modifier_definition::modifier_definition_size; ++mod_index) { + if(!(modifier_data.offsets[mod_index])) + break; // no more modifier values + + auto fixed_offset = modifier_data.offsets[mod_index]; + auto modifier_amount = modifier_data.values[mod_index]; + if(fixed_offset == sys::provincial_mod_offsets::movement_cost) { + move_cost_mod += modifier_amount; + } + } + terrain_cost[terrain] = move_cost_mod; + if(move_cost_mod > max_cost) { + max_cost = move_cost_mod; + } + } else { + terrain_cost[terrain] = 1.f; + } + } + + // rescale so 1 remains the same while max turns into 10 + + for(int terrain = 0; terrain < 64; terrain++) { + terrain_cost[terrain] = std::max(0.05f, terrain_cost[terrain] + (terrain_cost[terrain] - 1.f) / (max_cost - 1.f) * 10.f); + } + + auto step_count_hint = 10.f; + + world.for_each_province_adjacency([&](auto adj) { + if((world.province_adjacency_get_type(adj) & province::border::impassible_bit) == province::border::impassible_bit) { + map_state.map_data.railroad_starts.push_back(GLint(map_state.map_data.railroad_vertices.size())); + map_state.map_data.railroad_counts.push_back(0); + return; + } + + auto p1 = world.province_adjacency_get_connected_provinces(adj, 0); + auto p2 = world.province_adjacency_get_connected_provinces(adj, 1); + + //if (p1.index() >= province_definitions.first_sea_province.index()) { + // map_state.map_data.railroad_starts.push_back(GLint(map_state.map_data.railroad_vertices.size())); + // map_state.map_data.railroad_counts.push_back(0); + // return; + //} + //if (p2.index() >= province_definitions.first_sea_province.index()) { + // map_state.map_data.railroad_starts.push_back(GLint(map_state.map_data.railroad_vertices.size())); + // map_state.map_data.railroad_counts.push_back(0); + // return; + //} + bool sea_route = false; + if(p2.index() >= province_definitions.first_sea_province.index() || p1.index() >= province_definitions.first_sea_province.index()) { + sea_route = true; + } + + auto mid_point_1 = world.province_get_mid_point(p1); + auto mid_point_2 = world.province_get_mid_point(p2); + + auto mid_point_1_b = world.province_get_mid_point_b(p1); + auto mid_point_2_b = world.province_get_mid_point_b(p2); + + auto dist_y = abs(mid_point_2.y - mid_point_1.y); + auto dist_x = std::min( + abs(mid_point_2.x - mid_point_1.x), + std::min( + abs(mid_point_2.x - mid_point_1.x + map_state.map_data.size_x), + abs(mid_point_2.x - mid_point_1.x - map_state.map_data.size_x) + ) + ); + + //auto base_step_x = + + //auto base_step = + + uint8_t speed_direction = 0; + uint8_t speed_value = 1; + + auto start_x = (int)mid_point_1.x; + auto start_y = (int)mid_point_1.y; + auto start_idx = start_x + start_y * map_state.map_data.size_x; + + auto end_x = (int)mid_point_2.x; + auto end_y = (int)mid_point_2.y; + auto end_idx = end_x + end_y * map_state.map_data.size_x; + + { + pixel_queue.clear(); + pixel_heap_member pixel_start { start_x, start_y, 0.f, 0.f, 0, 0, 0 }; + pixel_queue.emplace_back(pixel_start); + } + + visited[start_idx] = encode_ternary(1, 0, 0); + + //assert(_CrtCheckMemory()); + + auto handle_next = [&](int x, int y, int next_x, int next_y, float base_distance, int8_t direction, int8_t dx, int8_t dy, uint8_t speed, float prev_dist) { + auto next_idx = next_x + next_y * map_state.map_data.size_x; + if(!visited[next_idx]) { + for(int i = 1; i <= (int)speed; i++) { + auto nx = x + dx * i; + auto ny = y + dy * i; + auto idx_to_set = nx + ny * map_state.map_data.size_x; + set_visited(idx_to_set, direction); + } + + set_visited(next_idx, direction); + auto local_terrain = map_state.map_data.terrain_id_map[next_idx]; + auto modifier = context.modifier_by_terrain_index[local_terrain]; + auto& modifier_data = world.modifier_get_province_values(modifier); + auto move_cost_mod = terrain_cost[local_terrain]; + if(sea_route) { + move_cost_mod = 100.f; + if (local_terrain == 255) move_cost_mod = 1.f; + } + auto next_distance = prev_dist + base_distance * std::max(0.05f, (move_cost_mod * 20.f)); + auto scaled_x = (float)next_x / (float)map_state.map_data.size_x; + auto scaled_y = (float)next_y / (float)map_state.map_data.size_y; + + glm::vec3 new_world_pos; + float angle_x = 2 * scaled_x * math::pi; + new_world_pos.x = math::cos(angle_x); + new_world_pos.y = math::sin(angle_x); + + float angle_y = scaled_y * math::pi; + new_world_pos.x *= math::sin(angle_y); + new_world_pos.y *= math::sin(angle_y); + new_world_pos.z = math::cos(angle_y); + + auto local_heur = glm::distance(mid_point_2_b, new_world_pos) * 100.f; + + pixel_heap_member to_add { next_x, next_y, next_distance, local_heur, dx, dy, uint8_t(std::min((int)speed, 20)) }; + pixel_queue.push_back(to_add); + std::push_heap(pixel_queue.begin(), pixel_queue.end(), std::greater<>{}); + } + }; + + while(true) { + // eliminate hopeless pixels + if(pixel_queue.size() >= 200) { + auto best_heuristic = pixel_queue[0].heur; + auto worst_heuristic = pixel_queue[0].heur; + for(size_t i = 0; i < pixel_queue.size(); i++) { + if(pixel_queue[i].heur > worst_heuristic) { + worst_heuristic = pixel_queue[i].heur; + } + if(pixel_queue[i].heur < best_heuristic) { + best_heuristic = pixel_queue[i].heur; + } + } + auto cutout = 0.25f * worst_heuristic + 0.75f * best_heuristic; + + for(int i = (int)pixel_queue.size() - 1; i >= 0; i--) { + if(pixel_queue[i].heur > cutout){ + pixel_queue.erase(pixel_queue.begin() + i); + } + } + std::make_heap(pixel_queue.begin(), pixel_queue.end()); + } + + auto best = pixel_queue[0]; + std::pop_heap(pixel_queue.begin(), pixel_queue.end(), std::greater<>{}); + pixel_queue.pop_back(); + + auto x = best.x; + auto y = best.y; + auto actual_distance = best.dist; + auto idx = best.x + best.y * map_state.map_data.size_x; + + for(int8_t dx = -1; dx <= 1; dx++) { + for(int8_t dy = -1; dy <= 1; dy++) { + if(dx == 0 && dy == 0) { + continue; + } + if(best.speed > 2 && dx * best.dx + dy * best.dy <= 0 && best.heur > 1.f) { + continue; + } + auto dir = encode_ternary(0, dx, dy); + // make additional "grid skipping" attempt which is supposedly faster + /* + if(best.dx == dx && best.dy == dy && best.heur > 1.f) { + uint8_t next_speed = best.speed +1; + if(next_speed > 10) { + next_speed = 10; + } + auto next_x = x + dx * next_speed; + auto next_y = y + dy * next_speed; + if(next_y < 0 || next_y >= (int)map_state.map_data.size_y) { + continue; + } + next_x = (next_x + (int)map_state.map_data.size_x) % (int)map_state.map_data.size_x; + handle_next(x, y, next_x, next_y, math::sqrt((float)(dx * dx + dy * dy)) * next_speed, dir, dx, dy, next_speed, actual_distance); + } + */ + { + auto next_x = x + dx; + auto next_y = y + dy; + if(next_y < 0 || next_y >= (int)map_state.map_data.size_y) { + continue; + } + next_x = (next_x + (int)map_state.map_data.size_x) % (int)map_state.map_data.size_x; + handle_next(x, y, next_x, next_y, math::sqrt((float)(dx * dx + dy * dy)), dir, dx, dy, 1, actual_distance); + } + } + } + + if(visited[end_idx]) { + break; + } + } + + map_state.map_data.railroad_starts.push_back(GLint(map_state.map_data.railroad_vertices.size())); + float distance = 0.f; + + int step = 0; + + glm::vec2 current_pos{ end_x, end_y }; + + + while(end_idx != start_idx && (int)(end_idx) >= 0) { + + auto y = end_idx / map_state.map_data.size_x; + auto x = end_idx - y * map_state.map_data.size_x; + + auto encoded_shift = (int)(visited[end_idx]); + + auto dy = (encoded_shift + 9) % 3; + if (dy == 2) dy = -1; + auto dx = (encoded_shift - dy) / 3; + + auto next_x = x - dx; + auto next_y = y - dy; + + auto next_idx = next_x + next_y * map_state.map_data.size_x; + + if(step % 3 != 0 && next_idx != (int)end_idx && step != 0) { + step++; + end_idx = next_idx; + continue; + } + step++; + + auto ny = next_idx / map_state.map_data.size_x; + auto nx = next_idx - ny * map_state.map_data.size_x; + glm::vec2 next_vec { nx, ny }; + glm::vec2 next_pos = put_in_local(next_vec, current_pos, float(map_state.map_data.size_x)); + glm::vec2 prev_perpendicular = glm::normalize(next_pos - current_pos); + + auto start_normal = glm::vec2(-prev_perpendicular.y, prev_perpendicular.x); + auto norm_pos = current_pos / glm::vec2(map_state.map_data.size_x, map_state.map_data.size_y); + auto norm_next = next_pos / glm::vec2(map_state.map_data.size_x, map_state.map_data.size_y); + + auto prev_distance = distance; + distance += glm::length(next_pos - current_pos) / float(map_state.map_data.size_y); + + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_pos, +start_normal, 0.0f, prev_distance });//C + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_pos, -start_normal, 1.0f, prev_distance });//D + + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, +start_normal, 0.0f, distance }); + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, -start_normal, 1.0f, distance }); + + end_idx = next_idx; + current_pos = next_pos; + } + + glm::vec2 next_pos = put_in_local(mid_point_1, current_pos, float(map_state.map_data.size_x)); + distance += glm::length(next_pos - current_pos) / float(map_state.map_data.size_y); + glm::vec2 prev_perpendicular = glm::normalize(mid_point_1 - current_pos); + auto start_normal = glm::vec2(-prev_perpendicular.y, prev_perpendicular.x); + auto norm_pos = current_pos / glm::vec2(map_state.map_data.size_x, map_state.map_data.size_y); + auto norm_next = next_pos / glm::vec2(map_state.map_data.size_x, map_state.map_data.size_y); + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_pos, +start_normal, 0.0f, 0.f });//C + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_pos, -start_normal, 1.0f, 0.f });//D + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, +start_normal, 0.0f, distance }); + map_state.map_data.railroad_vertices.emplace_back(map::textured_line_vertex{ norm_next, -start_normal, 1.0f, distance }); + + map_state.map_data.railroad_counts.push_back(GLsizei(map_state.map_data.railroad_vertices.size() - map_state.map_data.railroad_starts.back())); + + clear_visited(); + }); + + assert(_CrtCheckMemory()); // run pending triggers and effects for(auto pending_decision : pending_decisions) { @@ -3775,7 +4120,6 @@ void state::preload() { m.set_pop_support(0.0f); m.set_radicalism(0.0f); } - } void state::on_scenario_load() { @@ -4227,6 +4571,7 @@ void state::fill_unsaved_data() { // reconstructs derived values that are not di nations::monthly_flashpoint_update(*this); + // // clear any pending messages from previously loaded saves // diff --git a/src/gui/main_menu.cpp b/src/gui/main_menu.cpp index 1e32f8e725..f7a125459e 100644 --- a/src/gui/main_menu.cpp +++ b/src/gui/main_menu.cpp @@ -4111,6 +4111,7 @@ void main_menu_graphics_graphics_details_dropdown_t::on_update(sys::state& state add_item(int32_t(sys::graphics_mode::ugly), text::produce_simple_string(state, "graphics_details_0")); add_item(int32_t(sys::graphics_mode::classic), text::produce_simple_string(state, "graphics_details_1")); add_item(int32_t(sys::graphics_mode::modern), text::produce_simple_string(state, "graphics_details_2")); + add_item(int32_t(sys::graphics_mode::modern_classic), text::produce_simple_string(state, "graphics_details_3")); quiet_on_selection(state, int32_t(state.user_settings.graphics_mode)); // END } diff --git a/src/map/map.cpp b/src/map/map.cpp index 9ea4b9719a..d5651bff33 100644 --- a/src/map/map.cpp +++ b/src/map/map.cpp @@ -741,6 +741,7 @@ void display_data::load_shaders(simple_fs::directory& root) { shader_uniforms[i][uniform_time] = glGetUniformLocation(shaders[i], "time"); shader_uniforms[i][uniform_light_direction] = glGetUniformLocation(shaders[i], "light_direction"); shader_uniforms[i][uniform_ignore_light] = glGetUniformLocation(shaders[i], "ignore_light"); + shader_uniforms[i][uniform_railroad_level] = glGetUniformLocation(shaders[i], "railroad_level"); shader_uniforms[i][uniform_terrain_texture_sampler] = glGetUniformLocation(shaders[i], "terrain_texture_sampler"); shader_uniforms[i][uniform_terrainsheet_texture_sampler] = glGetUniformLocation(shaders[i], "terrainsheet_texture_sampler"); shader_uniforms[i][uniform_terrainsheet_texture_sampler_array] = glGetUniformLocation(shaders[i], "terrainsheet_texture_sampler_array"); @@ -1144,7 +1145,7 @@ void display_data::render( // Draw the railroads and city - if(/*zoom > map::zoom_close && */!railroad_vertices.empty() && state.user_settings.railroads_enabled) { + if(zoom > map::zoom_close && !railroad_vertices.empty() && state.user_settings.railroads_enabled) { glEnable(GL_BLEND); glBlendFuncSeparate(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA, GL_ONE, GL_ONE); @@ -1168,7 +1169,20 @@ void display_data::render( glBindVertexArray(vao_array[vo_railroad]); glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_railroad]); - glMultiDrawArrays(GL_TRIANGLE_STRIP, railroad_starts.data(), railroad_counts.data(), GLsizei(railroad_starts.size())); + + for(uint32_t idx = 0; idx < railroad_counts.size(); idx++) { + dcon::province_adjacency_id adj { (dcon::province_adjacency_id::value_base_t)idx }; + float railroad_level = 0.f; + if(state.world.province_adjacency_is_valid(adj)) { + auto p0 = state.world.province_adjacency_get_connected_provinces(adj, 0); + auto p1 = state.world.province_adjacency_get_connected_provinces(adj, 0); + auto rail0 = state.world.province_get_building_level(p0, uint8_t(economy::province_building_type::railroad)); + auto rail1 = state.world.province_get_building_level(p1, uint8_t(economy::province_building_type::railroad)); + railroad_level = (float)std::max(rail0, rail1); + } + glUniform1f(shader_uniforms[shader_railroad_line][uniform_railroad_level], railroad_level); + glDrawArrays(GL_TRIANGLE_STRIP, railroad_starts[idx], railroad_counts[idx]); + } } if(//zoom > map::zoom_close && @@ -1544,37 +1558,61 @@ void display_data::render( state.lookup_key("gfx_storage_commodity") )->second.definition ].data.image.gfx_object; - auto& gfx_def = state.ui_defs.gfx[gfx_id]; - auto frame = state.world.commodity_get_icon(state.selected_trade_good); - auto texture_handle = ogl::get_texture_handle(state, gfx_def.primary_texture_handle, gfx_def.is_partially_transparent()); - - glActiveTexture(GL_TEXTURE0); - glBindTexture(GL_TEXTURE_2D, texture_handle); + auto& gfx_def = state.ui_defs.gfx[gfx_id]; + auto frame = state.world.commodity_get_icon(state.selected_trade_good); + auto texture_handle = ogl::get_texture_handle(state, gfx_def.primary_texture_handle, gfx_def.is_partially_transparent()); - glUniform1i(shader_uniforms[shader_map_sprite][uniform_texture_sampler], 0); - glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_start], (float)frame / gfx_def.number_of_frames, 0.f); - glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_size], 1.f / gfx_def.number_of_frames, 1.f); - - glBindVertexArray(vao_array[vo_square]); - glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_square]); - const float speed = 0.5f; + const float speed = 0.5f; - bool spawned_something = false; + bool spawned_something = false; + { for(size_t i = 0; i < trade_particles_positions.size(); i++) { auto& p = trade_particles_positions[i]; // update movement if(p.trade_graph_node_current != -1 && p.trade_graph_node_next != -1) { - auto direction = p.target_ - p.position_; - auto length = float(glm::length(direction)); - if(length < speed * 2) { - p.trade_graph_node_prev = p.trade_graph_node_current; - p.trade_graph_node_current = p.trade_graph_node_next; - p.trade_graph_node_next = -1; - } else { - p.position_ += direction / length * speed; + if(p.adj_index != -1) { + auto time_left = 1.f; + while(time_left > 0.f) { + auto actual_target_offset = railroad_starts[p.adj_index] + p.adj_count; + auto actual_target = railroad_vertices[actual_target_offset].position_ * glm::vec2(float(size_x), float(size_y)); + auto direction = actual_target - p.position_; + auto length = float(glm::length(direction)); + if(length < speed * 2) { + p.adj_count += p.adj_direction; + if(p.adj_count >= railroad_counts[p.adj_index] || p.adj_count < 0) { + p.trade_graph_node_prev = p.trade_graph_node_current; + p.trade_graph_node_current = p.trade_graph_node_next; + p.trade_graph_node_next = -1; + p.adj_index = -1; + break; + } + time_left -= length / (speed * 2); + } else { + for(int vagon = (int)p.vagon_positions.size() - 1; vagon > 0; vagon--) { + p.vagon_positions[vagon] = p.vagon_positions[vagon - 1]; + } + p.vagon_positions[0] = p.position_; + p.position_ += direction / length * speed; + time_left -= 1.f; + } + } + } else { + auto direction = p.target_ - p.position_; + auto length = float(glm::length(direction)); + if(length < speed * 2) { + p.trade_graph_node_prev = p.trade_graph_node_current; + p.trade_graph_node_current = p.trade_graph_node_next; + p.trade_graph_node_next = -1; + } else { + for(int vagon = (int)p.vagon_positions.size() - 1; vagon > 0; vagon--) { + p.vagon_positions[vagon] = p.vagon_positions[vagon - 1]; + } + p.vagon_positions[0] = p.position_; + p.position_ += direction / length * speed; + } } } @@ -1601,16 +1639,34 @@ void display_data::render( p.trade_graph_node_current = -1; } else { p.trade_graph_node_next = target; + if(target >= (int)state.world.province_size()) { + target -= state.world.province_size(); + } p.target_ = put_in_local(trade_node_position[target], p.position_, (float)size_x); + + auto current = p.trade_graph_node_current; + if(current >= (int)state.world.province_size()) { + current -= state.world.province_size(); + } + + auto adj = state.world.get_province_adjacency_by_province_pair( + dcon::province_id{ dcon::province_id::value_base_t(target) }, + dcon::province_id{ dcon::province_id::value_base_t(current) } + ); + if(adj && railroad_counts[adj.index()] > 0) { + p.adj_index = adj.index(); + p.adj_count = 0; + p.adj_direction = 1; + if(state.world.province_adjacency_get_connected_provinces(adj, 0).index() != target) { + p.adj_count = railroad_counts[adj.index()] - 1; + p.adj_direction = -1; + } + } } } - if(p.trade_graph_node_current != -1) { - glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], trade_particles_positions[i].position_.x / float(size_x), trade_particles_positions[i].position_.y / float(size_y)); - glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_scale], 4.f / float(size_x), 4.f / float(size_y)); - glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); - } + // spawn "new" particles // don't spawn them too often @@ -1638,6 +1694,96 @@ void display_data::render( } } } + } + // draw + { + + glEnable(GL_BLEND); + glBlendEquation(GL_FUNC_ADD); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, texture_handle); + + glUniform1i(shader_uniforms[shader_map_sprite][uniform_texture_sampler], 0); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_start], (float)frame / gfx_def.number_of_frames, 0.f); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_size], 1.f / gfx_def.number_of_frames, 1.f); + + glBindVertexArray(vao_array[vo_square]); + glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_square]); + + for(size_t i = 0; i < trade_particles_positions.size(); i++) { + auto& p = trade_particles_positions[i]; + if(p.trade_graph_node_current != -1) { + for(int vagon = (int)p.vagon_positions.size() - 1; vagon > 0; vagon--) { + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], trade_particles_positions[i].vagon_positions[vagon].x / float(size_x), trade_particles_positions[i].vagon_positions[vagon].y / float(size_y)); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_scale], 1.f / float(size_x), 1.f / float(size_y)); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + } + } + + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textures[texture_train]); + + glUniform1i(shader_uniforms[shader_map_sprite][uniform_texture_sampler], 0); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_start], 0.f, 0.f); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_size], 1.f, 1.f); + + glBindVertexArray(vao_array[vo_square]); + glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_square]); + + for(size_t i = 0; i < trade_particles_positions.size(); i++) { + auto& p = trade_particles_positions[i]; + + dcon::province_adjacency_id adj{ dcon::province_adjacency_id::value_base_t(p.adj_index) }; + auto p1 = state.world.province_adjacency_get_connected_provinces(adj, 0); + auto p2 = state.world.province_adjacency_get_connected_provinces(adj, 1); + auto p1_is_sea = p1.index() >= state.province_definitions.first_sea_province.index() && p1.index() < (int)state.world.province_size(); + auto p2_is_sea = p2.index() >= state.province_definitions.first_sea_province.index() && p2.index() < (int)state.world.province_size(); + + if(p1_is_sea || p2_is_sea) { + continue; + } + + if(p.trade_graph_node_current != -1) { + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], trade_particles_positions[i].position_.x / float(size_x), trade_particles_positions[i].position_.y / float(size_y)); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_scale], 1.f / float(size_x), 1.f / float(size_y)); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + } + + glActiveTexture(GL_TEXTURE0); + glBindTexture(GL_TEXTURE_2D, textures[texture_ship]); + + glUniform1i(shader_uniforms[shader_map_sprite][uniform_texture_sampler], 0); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_start], 0.f, 0.f); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_texture_size], 1.f, 1.f); + + glBindVertexArray(vao_array[vo_square]); + glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_square]); + + for(size_t i = 0; i < trade_particles_positions.size(); i++) { + auto& p = trade_particles_positions[i]; + + dcon::province_adjacency_id adj{ dcon::province_adjacency_id::value_base_t(p.adj_index) }; + auto p1 = state.world.province_adjacency_get_connected_provinces(adj, 0); + auto p2 = state.world.province_adjacency_get_connected_provinces(adj, 1); + auto p1_is_sea = p1.index() >= state.province_definitions.first_sea_province.index() && p1.index() < (int)state.world.province_size(); + auto p2_is_sea = p2.index() >= state.province_definitions.first_sea_province.index() && p2.index() < (int)state.world.province_size(); + + if(!(p1_is_sea || p2_is_sea)) { + continue; + } + + if(p.trade_graph_node_current != -1) { + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_offsets], trade_particles_positions[i].position_.x / float(size_x), trade_particles_positions[i].position_.y / float(size_y)); + glUniform2f(shader_uniforms[shader_map_sprite][uniform_sprite_scale], 1.f / float(size_x), 1.f / float(size_y)); + glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + } + } + } } } @@ -3231,6 +3377,7 @@ void display_data::update_sprawl(sys::state& state) { } }; + /* railroad_vertices.clear(); railroad_starts.clear(); railroad_counts.clear(); @@ -3255,6 +3402,7 @@ void display_data::update_sprawl(sys::state& state) { assert(railroad_counts.back() > 1); } assert(railroad_counts.size() == railroad_starts.size()); + */ if(!railroad_vertices.empty()) { glBindBuffer(GL_ARRAY_BUFFER, vbo_array[vo_railroad]); @@ -4324,6 +4472,11 @@ void display_data::load_map(sys::state& state) { textures[texture_river_body] = load_dds_texture(assets_dir, NATIVE("river.dds")); ogl::set_gltex_parameters(textures[texture_river_body], GL_TEXTURE_2D, GL_LINEAR_MIPMAP_LINEAR, GL_REPEAT); + textures[texture_train] = ogl::make_gl_texture(assets_dir, NATIVE("images/train.png")); + ogl::set_gltex_parameters(textures[texture_train], GL_TEXTURE_2D, GL_LINEAR_MIPMAP_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + textures[texture_ship] = ogl::make_gl_texture(assets_dir, NATIVE("images/cargo-ship.png")); + ogl::set_gltex_parameters(textures[texture_ship], GL_TEXTURE_2D, GL_LINEAR_MIPMAP_LINEAR, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE); + textures[texture_national_border] = load_dds_texture(assets_dir, NATIVE("nat_border.dds")); ogl::set_gltex_parameters(textures[texture_national_border], GL_TEXTURE_2D, GL_LINEAR_MIPMAP_LINEAR, GL_REPEAT, GL_CLAMP_TO_EDGE); diff --git a/src/map/map.hpp b/src/map/map.hpp index 36d00dc244..8e1a7f7c08 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -39,6 +39,10 @@ struct trade_particle { int trade_graph_node_current; int trade_graph_node_prev = -1; int trade_graph_node_next; + int adj_index = -1; + int adj_count = 0; + int adj_direction = 1; + std::array vagon_positions; }; struct screen_vertex { screen_vertex(float x, float y) : position_(x, y){}; @@ -363,7 +367,9 @@ class display_data { static constexpr uint32_t texture_printbrush = 28; static constexpr uint32_t texture_hatching = 29; static constexpr uint32_t texture_watercolor = 30; - static constexpr uint32_t texture_count = 31; + static constexpr uint32_t texture_train = 31; + static constexpr uint32_t texture_ship = 32; + static constexpr uint32_t texture_count = 33; GLuint textures[texture_count] = { 0 }; // Texture Array static constexpr uint32_t texture_array_terrainsheet = 0; @@ -443,7 +449,8 @@ class display_data { static constexpr uint32_t uniform_printbrush = 49; static constexpr uint32_t uniform_hatching = 50; static constexpr uint32_t uniform_watercolor = 51; - static constexpr uint32_t uniform_count = 52; + static constexpr uint32_t uniform_railroad_level = 52; + static constexpr uint32_t uniform_count = 53; GLint shader_uniforms[shader_count][uniform_count] = { }; // models: Textures for static meshes From 065958c81aa1042cca7381899a7715ea07baf0cf Mon Sep 17 00:00:00 2001 From: ineveraskedforthis Date: Sat, 8 Aug 2026 13:40:54 +0300 Subject: [PATCH 2/4] fix clang compilation --- src/gamestate/system_state.cpp | 2 +- src/map/map.hpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/gamestate/system_state.cpp b/src/gamestate/system_state.cpp index 95a99cd37d..7654a185bb 100644 --- a/src/gamestate/system_state.cpp +++ b/src/gamestate/system_state.cpp @@ -3952,7 +3952,7 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day auto next_idx = next_x + next_y * map_state.map_data.size_x; - if(step % 3 != 0 && next_idx != (int)end_idx && step != 0) { + if(step % 3 != 0 && next_idx != end_idx && step != 0) { step++; end_idx = next_idx; continue; diff --git a/src/map/map.hpp b/src/map/map.hpp index 8e1a7f7c08..455e3d13cf 100644 --- a/src/map/map.hpp +++ b/src/map/map.hpp @@ -42,7 +42,7 @@ struct trade_particle { int adj_index = -1; int adj_count = 0; int adj_direction = 1; - std::array vagon_positions; + std::array vagon_positions {} ; }; struct screen_vertex { screen_vertex(float x, float y) : position_(x, y){}; From a81b4113074e3daac7d4050a55c8ffde3390575c Mon Sep 17 00:00:00 2001 From: ineveraskedforthis Date: Sat, 8 Aug 2026 13:53:40 +0300 Subject: [PATCH 3/4] remove memory check --- src/gamestate/system_state.cpp | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/gamestate/system_state.cpp b/src/gamestate/system_state.cpp index 7654a185bb..adc1d55ac5 100644 --- a/src/gamestate/system_state.cpp +++ b/src/gamestate/system_state.cpp @@ -3686,8 +3686,6 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day // generate a road for every adjacency - assert(_CrtCheckMemory()); - std::vector visited; visited.resize(map_state.map_data.size_x * map_state.map_data.size_y); @@ -3811,7 +3809,6 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day visited[start_idx] = encode_ternary(1, 0, 0); - //assert(_CrtCheckMemory()); auto handle_next = [&](int x, int y, int next_x, int next_y, float base_distance, int8_t direction, int8_t dx, int8_t dy, uint8_t speed, float prev_dist) { auto next_idx = next_x + next_y * map_state.map_data.size_x; @@ -3998,7 +3995,6 @@ void state::load_scenario_data(parsers::error_handler& err, sys::year_month_day clear_visited(); }); - assert(_CrtCheckMemory()); // run pending triggers and effects for(auto pending_decision : pending_decisions) { From 5f9e24846b6f7ade461c3fda774619712571d72b Mon Sep 17 00:00:00 2001 From: ineveraskedforthis Date: Mon, 10 Aug 2026 19:29:42 +0300 Subject: [PATCH 4/4] final shader adjustments --- assets/shaders/glsl/map_f.glsl | 147 +++++++++++++++++++++------------ 1 file changed, 96 insertions(+), 51 deletions(-) diff --git a/assets/shaders/glsl/map_f.glsl b/assets/shaders/glsl/map_f.glsl index f3cae42dfa..2c01935ddd 100644 --- a/assets/shaders/glsl/map_f.glsl +++ b/assets/shaders/glsl/map_f.glsl @@ -261,32 +261,52 @@ vec4 get_land_political_modern() { vec4 get_land_political_gores() { float s = space_coords.z; float t = space_coords.y; - float h = space_coords.x; - - float angle = atan(t, s); - - float vertical_angle = asin(h); - - float radius = sqrt(s * s + t * t); - - float equator_segment_x = fract(angle * 36.f / 2.f / PI); - float equator_segment_number = floor(angle * 36.f / 2.f / PI); - float vertical_segment = fract(vertical_angle * 2.f / PI * 8.f); - float vertical_segment_number = floor(vertical_angle * 2.f / PI * 8.f); - - float horisontal_coord_before_cutting_paper = (equator_segment_x - 0.5f) * radius + 0.5f; - - float line_width = 0.0075f; - - float v1 = max(0, line_width - equator_segment_x * (1.f - equator_segment_x)); - float v2 = max(0.f, line_width - vertical_segment * (1.f - vertical_segment)); - float line_mult = 1.f - (v1 + v2) * (v1 + v2) * 10000.f; + float angle = tex_coord.x * PI * 2.f; + float vertical_angle = (tex_coord.y - 0.5f) * PI; //asin(h); + float radius = cos(vertical_angle); + float big_line_divisor = 3.f; + float equator_segment_x = fract(angle * 48.f / 2.f / PI * big_line_divisor); + float equator_segment_number = mod(floor(angle * 48.f / 2.f / PI * big_line_divisor + 0.5f), 36.f); + float equator_segment_number_area = mod(floor(angle * 48.f / 2.f / PI * big_line_divisor), 36.f); + float vertical_segment = fract(vertical_angle * 2.f / PI * 12.f * big_line_divisor); + float vertical_segment_number = floor(vertical_angle * 2.f / PI * 12.f * big_line_divisor + 0.5f); + float vertical_segment_number_area = floor(vertical_angle * 2.f / PI * 12.f * big_line_divisor); + float horisontal_coord_before_cutting_paper = (equator_segment_x - 0.5f) * radius + 0.5f; + float line_width = 0.015f / min(1.f, zoom / 6.f); + float line_width_v = line_width; + float line_width_h = line_width; + float softness_mult = (1.2f + abs(vertical_angle) / 2.f) / sqrt(min(1.f, zoom / 6.f)); + float softness_mod = 1.f; + float inside_v = 1.f; + if (mod(round(equator_segment_number), 4.f) == 0.f) { + line_width_v = line_width_v * 3.f / 2.f; + inside_v = 0.f; + } + float inside_h = 1.f; + if (mod(round(vertical_segment_number), 4.f) == 0.f) { + line_width_h = line_width_h * 3.f / 2.f; + inside_h = 0.f; + } + float base_unit = cos(vertical_angle); + float length_mult = 1.f / base_unit; + if (radius < 0.2f) { + length_mult = 1.f / radius; + } + float line_mult = 1.f; + float v1 = abs(fract(equator_segment_x + 0.5f) - 0.5f); + if (v1 < line_width_v * length_mult) { + line_mult = min(line_mult, 0.2f * softness_mult * (1.f + 0.5f * inside_v) + max(0.5f - zoom / 100.f, smootherstep(v1 / (line_width_v * length_mult)))); + } + float v2 = abs(fract(vertical_segment + 0.5f) - 0.5f); + if (v2 < line_width_h) { + line_mult = min(line_mult, 0.2f * softness_mult * (1.f + 0.5f * inside_h) + max(0.5f - zoom / 100.f, smootherstep(v2 / (line_width_h)))); + } vec4 wc = texture(watercolor, vec2(horisontal_coord_before_cutting_paper, vertical_segment) * 10.f); vec4 wc_global = texture(watercolor, vec2(horisontal_coord_before_cutting_paper, vertical_segment) / 2.f); - float hatching = texture(hatching, vec2(horisontal_coord_before_cutting_paper * (mod(vertical_segment_number, 2.f) * 2.f - 1.f), vertical_segment * (mod(equator_segment_number, 2.f) * 2.f - 1.f)) * 20.f).r; + float hatching = texture(hatching, vec2(horisontal_coord_before_cutting_paper * (mod(vertical_segment_number_area, 2.f) * 2.f - 1.f), vertical_segment * (mod(equator_segment_number_area, 2.f) * 2.f - 1.f)) * 4.f).r; vec4 terrain = get_terrain_mix(); float is_land = terrain.a; @@ -299,8 +319,10 @@ vec4 get_land_political_gores() { prov_highlight = vec4(hatching, hatching, hatching, 1.f); } vec4 prov_color = texture(province_color, vec3(prov_id, 0.)); + vec4 stripe_color = texture(province_color, vec3(prov_id, 1.)); + //float stripeFactor = texture(stripes_texture, stripe_coord).a; //vec4 base_color = vec4(1.f, 247.f / 255.f, 220.f / 255.f, 1.f) * 0.9f; - vec4 base_color = vec4(0.93, 0.90, 0.83, 1.f); + vec4 base_color = vec4(0.80, 0.76, 0.63, 1.f); float gradient = sqrt(to_national_border); const vec3 GREYIFY = vec3( 0.212671, 0.715160, 0.072169 ); @@ -314,17 +336,20 @@ vec4 get_land_political_gores() { float wc_g = dot(wc.xyz, GREYIFY); float final_weight = min(1, sqrt( - weight_base - + wc_g * weight_watercolor - + weight_border * (1.f - gradient) - + hatching * weight_hatching - )); - + weight_base + + wc_g * weight_watercolor + + weight_border * (1.f - gradient) + + hatching * weight_hatching + )); + + if (stripe_color != prov_color) { + base_color = stripe_color * final_weight + base_color * (1.f - final_weight); + } vec4 mixed_color = prov_color * final_weight + base_color * (1.f - final_weight); //vec4 color = prov_color * wc - vec4 result = (mixed_color - 0.f * hatching) * min(1.f, 0.3f + line_mult * line_mult) * (0.85f + 0.05f * wc + 0.10f * dot(wc_global.xyz, GREYIFY)); + vec4 result = (mixed_color - 0.f * hatching) * min(1.f, line_mult) * (0.85f + 0.05f * wc + 0.10f * dot(wc_global.xyz, GREYIFY)) * min(1.f, 0.2f + gradient); result.a = is_land; return result; @@ -479,33 +504,53 @@ vec4 get_land() { vec4 get_water_gores() { float s = space_coords.z; float t = space_coords.y; - float h = space_coords.x; - - float angle = atan(t, s); - - float vertical_angle = asin(h); - - float radius = sqrt(s * s + t * t); - - float equator_segment_x = fract(angle * 36.f / 2.f / PI); - float equator_segment_number = floor(angle * 36.f / 2.f / PI); - float vertical_segment = fract(vertical_angle * 2.f / PI * 8.f); - float vertical_segment_number = floor(vertical_angle * 2.f / PI * 8.f); - - float horisontal_coord_before_cutting_paper = (equator_segment_x - 0.5f) * radius + 0.5f; - - float line_width = 0.0075f; - - float v1 = max(0, line_width - equator_segment_x * (1.f - equator_segment_x)); - float v2 = max(0.f, line_width - vertical_segment * (1.f - vertical_segment)); - float line_mult = 1.f - (v1 + v2) * (v1 + v2) * 10000.f; + float angle = tex_coord.x * PI * 2.f; + float vertical_angle = (tex_coord.y - 0.5f) * PI; //asin(h); + float radius = cos(vertical_angle); + float big_line_divisor = 3.f; + float equator_segment_x = fract(angle * 48.f / 2.f / PI * big_line_divisor); + float equator_segment_number = mod(floor(angle * 48.f / 2.f / PI * big_line_divisor + 0.5f), 36.f); + float equator_segment_number_area = mod(floor(angle * 48.f / 2.f / PI * big_line_divisor), 36.f); + float vertical_segment = fract(vertical_angle * 2.f / PI * 12.f * big_line_divisor); + float vertical_segment_number = floor(vertical_angle * 2.f / PI * 12.f * big_line_divisor + 0.5f); + float vertical_segment_number_area = floor(vertical_angle * 2.f / PI * 12.f * big_line_divisor); + float horisontal_coord_before_cutting_paper = (equator_segment_x - 0.5f) * radius + 0.5f; + float line_width = 0.015f / min(1.f, zoom / 6.f); + float line_width_v = line_width; + float line_width_h = line_width; + float softness_mult = (1.f + abs(vertical_angle) / 2.f) / sqrt(min(1.f, zoom / 6.f)); + float softness_mod = 1.f; + float inside_v = 1.f; + if (mod(round(equator_segment_number), 4.f) == 0.f) { + line_width_v = line_width_v * 3.f / 2.f; + inside_v = 0.f; + } + float inside_h = 1.f; + if (mod(round(vertical_segment_number), 4.f) == 0.f) { + line_width_h = line_width_h * 3.f / 2.f; + inside_h = 0.f; + } + float base_unit = cos(vertical_angle); + float length_mult = 1.f / base_unit; + if (radius < 0.2f) { + length_mult = 1.f / radius; + } + float line_mult = 1.f; + float v1 = abs(fract(equator_segment_x + 0.5f) - 0.5f); + if (v1 < line_width_v * length_mult) { + line_mult = min(line_mult, 0.2f * softness_mult * (1.f + 0.5f * inside_v) + max(0.5f - zoom / 100.f, smootherstep(v1 / (line_width_v * length_mult)))); + } + float v2 = abs(fract(vertical_segment + 0.5f) - 0.5f); + if (v2 < line_width_h) { + line_mult = min(line_mult, 0.2f * softness_mult * (1.f + 0.5f * inside_h) + max(0.5f - zoom / 100.f, smootherstep(v2 / (line_width_h)))); + } vec4 wc = texture(watercolor, vec2(horisontal_coord_before_cutting_paper, vertical_segment) * 10.f); vec4 wc_global = texture(watercolor, vec2(horisontal_coord_before_cutting_paper, vertical_segment) / 2.f); - float hatching = 0.2f * texture(hatching, vec2(horisontal_coord_before_cutting_paper * (mod(vertical_segment_number, 2.f) * 2.f - 1.f), vertical_segment * (mod(equator_segment_number, 2.f) * 2.f - 1.f)) * 20.f).r; - return vec4(0.77f - hatching, 0.85f - hatching, 0.80f, 1.f) * (0.25f + 0.5f * wc + 0.25f * wc_global) * min(1.f, line_mult * line_mult); + float hatching = 0.4f * texture(hatching, vec2(horisontal_coord_before_cutting_paper * (mod(vertical_segment_number_area, 2.f) * 2.f - 1.f), vertical_segment * (mod(equator_segment_number_area, 2.f) * 2.f - 1.f)) * 4.f).r; + return vec4(0.57f - hatching, 0.65f - hatching, 0.70f, 1.f) * (0.25f + 0.5f * wc + 0.25f * wc_global) * vec4(0.2f + 0.8f * line_mult, 0.2f + 0.8f *line_mult, 1.f, 1.f); } vec4 get_water() {