From 2d47ff8c02a4ee88270634289381140f8ac1617e Mon Sep 17 00:00:00 2001 From: Hop311 Date: Wed, 5 Apr 2023 18:38:40 +0100 Subject: Custom map mesh that fixes UV issue --- extension/src/MapMesh.cpp | 146 +++++++++++++++++++++++++++++++ extension/src/MapMesh.hpp | 33 +++++++ extension/src/register_types.cpp | 3 + game/src/GameSession/MapView.gd | 32 +++---- game/src/GameSession/MapView.tscn | 8 +- game/src/GameSession/TerrainMap.gdshader | 16 +--- 6 files changed, 203 insertions(+), 35 deletions(-) create mode 100644 extension/src/MapMesh.cpp create mode 100644 extension/src/MapMesh.hpp diff --git a/extension/src/MapMesh.cpp b/extension/src/MapMesh.cpp new file mode 100644 index 0000000..2fa6be4 --- /dev/null +++ b/extension/src/MapMesh.cpp @@ -0,0 +1,146 @@ +#include "MapMesh.hpp" + +#include + +using namespace godot; +using namespace OpenVic2; + +void MapMesh::_bind_methods() { + ClassDB::bind_method(D_METHOD("set_aspect_ratio", "ratio"), &MapMesh::set_aspect_ratio); + ClassDB::bind_method(D_METHOD("get_aspect_ratio"), &MapMesh::get_aspect_ratio); + + ClassDB::bind_method(D_METHOD("set_repeat_proportion", "proportion"), &MapMesh::set_repeat_proportion); + ClassDB::bind_method(D_METHOD("get_repeat_proportion"), &MapMesh::get_repeat_proportion); + + ClassDB::bind_method(D_METHOD("set_subdivide_width", "divisions"), &MapMesh::set_subdivide_width); + ClassDB::bind_method(D_METHOD("get_subdivide_width"), &MapMesh::get_subdivide_width); + + ClassDB::bind_method(D_METHOD("set_subdivide_depth", "divisions"), &MapMesh::set_subdivide_depth); + ClassDB::bind_method(D_METHOD("get_subdivide_depth"), &MapMesh::get_subdivide_depth); + + ClassDB::bind_method(D_METHOD("get_core_aabb"), &MapMesh::get_core_aabb); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "aspect_ratio", PROPERTY_HINT_NONE, "suffix:m"), "set_aspect_ratio", "get_aspect_ratio"); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "repeat_proportion", PROPERTY_HINT_NONE, "suffix:m"), "set_repeat_proportion", "get_repeat_proportion"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_width", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_width", "get_subdivide_width"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "subdivide_depth", PROPERTY_HINT_RANGE, "0,100,1,or_greater"), "set_subdivide_depth", "get_subdivide_depth"); +} + +void MapMesh::_request_update() { + // Hack to trigger _update_lightmap_size and _request_update in PrimitiveMesh + set_add_uv2(get_add_uv2()); +} + +void MapMesh::set_aspect_ratio(const float ratio) { + aspect_ratio = ratio; + _request_update(); +} + +float MapMesh::get_aspect_ratio() const { + return aspect_ratio; +} + +void MapMesh::set_repeat_proportion(const float proportion) { + repeat_proportion = proportion; + _request_update(); +} + +float MapMesh::get_repeat_proportion() const { + return repeat_proportion; +} + +void MapMesh::set_subdivide_width(const int divisions) { + subdivide_w = divisions > 0 ? divisions : 0; + _request_update(); +} + +int MapMesh::get_subdivide_width() const { + return subdivide_w; +} + +void MapMesh::set_subdivide_depth(const int divisions) { + subdivide_d = divisions > 0 ? divisions : 0; + _request_update(); +} + +int MapMesh::get_subdivide_depth() const { + return subdivide_d; +} + +AABB MapMesh::get_core_aabb() const { + const Vector3 size{ aspect_ratio, 0.0f, 1.0f }; + return AABB{ size * -0.5f, size }; +} + +Array MapMesh::_create_mesh_array() const { + Array arr; + arr.resize(Mesh::ARRAY_MAX); + + const int vertex_count = (subdivide_w + 2) * (subdivide_d + 2); + const int indice_count = (subdivide_w + 1) * (subdivide_d + 1) * 6; + + PackedVector3Array points; + PackedVector3Array normals; + PackedFloat32Array tangents; + PackedVector2Array uvs; + PackedInt32Array indices; + + points.resize(vertex_count); + normals.resize(vertex_count); + tangents.resize(vertex_count * 4); + uvs.resize(vertex_count); + indices.resize(indice_count); + + static const Vector3 normal{ 0.0f, 1.0f, 0.0f }; + const Size2 uv_size{ 1.0f + 2.0f * repeat_proportion, 1.0f }; + const Size2 size{ aspect_ratio * uv_size.x, uv_size.y }, start_pos = size * -0.5f; + + int point_index = 0, thisrow = 0, prevrow = 0, indice_index = 0; + Vector2 subdivide_step{ 1.0f / (subdivide_w + 1.0f) , 1.0f / (subdivide_d + 1.0f) }; + Vector3 point{ 0.0f, 0.0f, start_pos.y }; + Vector2 point_step = subdivide_step * size; + Vector2 uv{}, uv_step = subdivide_step * uv_size; + + for (int j = 0; j <= subdivide_d + 1; ++j) { + point.x = start_pos.x; + uv.x = -repeat_proportion; + + for (int i = 0; i <= subdivide_w + 1; ++i) { + + points[point_index] = point; + normals[point_index] = normal; + tangents[point_index * 4 + 0] = 1.0f; + tangents[point_index * 4 + 1] = 0.0f; + tangents[point_index * 4 + 2] = 0.0f; + tangents[point_index * 4 + 3] = 1.0f; + uvs[point_index] = uv; + point_index++; + + if (i > 0 && j > 0) { + indices[indice_index + 0] = prevrow + i - 1; + indices[indice_index + 1] = prevrow + i; + indices[indice_index + 2] = thisrow + i - 1; + indices[indice_index + 3] = prevrow + i; + indices[indice_index + 4] = thisrow + i; + indices[indice_index + 5] = thisrow + i - 1; + indice_index += 6; + } + + point.x += point_step.x; + uv.x += uv_step.x; + } + + point.z += point_step.y; + uv.y += uv_step.y; + prevrow = thisrow; + thisrow = point_index; + } + + arr[Mesh::ARRAY_VERTEX] = points; + arr[Mesh::ARRAY_NORMAL] = normals; + arr[Mesh::ARRAY_TANGENT] = tangents; + arr[Mesh::ARRAY_TEX_UV] = uvs; + arr[Mesh::ARRAY_INDEX] = indices; + + return arr; +} diff --git a/extension/src/MapMesh.hpp b/extension/src/MapMesh.hpp new file mode 100644 index 0000000..5427894 --- /dev/null +++ b/extension/src/MapMesh.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include + +namespace OpenVic2 { + class MapMesh : public godot::PrimitiveMesh { + GDCLASS(MapMesh, godot::PrimitiveMesh) + + float aspect_ratio = 2.0f, repeat_proportion = 0.5f; + int subdivide_w = 0, subdivide_d = 0; + + protected: + static void _bind_methods(); + void _request_update(); + + public: + void set_aspect_ratio(const float ratio); + float get_aspect_ratio() const; + + void set_repeat_proportion(const float proportion); + float get_repeat_proportion() const; + + void set_subdivide_width(const int divisions); + int get_subdivide_width() const; + + void set_subdivide_depth(const int divisions); + int get_subdivide_depth() const; + + godot::AABB get_core_aabb() const; + + godot::Array _create_mesh_array() const override; + }; +} diff --git a/extension/src/register_types.cpp b/extension/src/register_types.cpp index 3ef0c24..16e59b2 100644 --- a/extension/src/register_types.cpp +++ b/extension/src/register_types.cpp @@ -10,6 +10,7 @@ #include "Checksum.hpp" #include "LoadLocalisation.hpp" #include "MapSingleton.hpp" +#include "MapMesh.hpp" using namespace godot; using namespace OpenVic2; @@ -44,6 +45,8 @@ void initialize_openvic2_types(ModuleInitializationLevel p_level) { ClassDB::register_class(); _map_singleton = memnew(MapSingleton); Engine::get_singleton()->register_singleton("MapSingleton", MapSingleton::get_singleton()); + + ClassDB::register_class(); } void uninitialize_openvic2_types(ModuleInitializationLevel p_level) { diff --git a/game/src/GameSession/MapView.gd b/game/src/GameSession/MapView.gd index a8b4dcc..4a44bce 100644 --- a/game/src/GameSession/MapView.gd +++ b/game/src/GameSession/MapView.gd @@ -31,10 +31,10 @@ const _shader_param_selected_pos : StringName = &"selected_pos" get: return _zoom_target set(v): _zoom_target = clamp(v, _zoom_target_min, _zoom_target_max) -@export var _map_mesh : MeshInstance3D +@export var _map_mesh_instance : MeshInstance3D +var _map_mesh : MapMesh var _map_shader_material : ShaderMaterial var _map_province_shape_image : Image -var _map_aspect_ratio : float = 1.0 var _map_mesh_corner : Vector2 var _map_mesh_dims : Vector2 @@ -46,22 +46,21 @@ func _ready(): if _camera == null: push_error("MapView's _camera variable hasn't been set!") return - if _map_mesh == null: + if _map_mesh_instance == null: push_error("MapView's _map_mesh variable hasn't been set!") return _map_province_shape_image = MapSingleton.get_province_shape_image() if _map_province_shape_image == null: push_error("Failed to get province shape image!") return + if not _map_mesh_instance.mesh is MapMesh: + push_error("Invalid map mesh class: ", _map_mesh_instance.mesh.get_class(), "(expected MapMesh)") + return + _map_mesh = _map_mesh_instance.mesh # Set map mesh size and get bounds - _map_aspect_ratio = float(_map_province_shape_image.get_width()) / float(_map_province_shape_image.get_height()) - if _map_mesh.mesh is PlaneMesh: - # Width is doubled so that the map appears to loop horizontally - (_map_mesh.mesh as PlaneMesh).size = Vector2(_map_aspect_ratio * 2, 1) - else: - push_error("Invalid map mesh class: ", _map_mesh.mesh.get_class(), "(expected PlaneMesh)") - var map_mesh_aabb := _map_mesh.get_aabb() * _map_mesh.transform + _map_mesh.aspect_ratio = float(_map_province_shape_image.get_width()) / float(_map_province_shape_image.get_height()) + var map_mesh_aabb := _map_mesh.get_core_aabb() * _map_mesh_instance.transform _map_mesh_corner = Vector2( min(map_mesh_aabb.position.x, map_mesh_aabb.end.x), min(map_mesh_aabb.position.z, map_mesh_aabb.end.z) @@ -71,7 +70,7 @@ func _ready(): map_mesh_aabb.position.z - map_mesh_aabb.end.z )) - var map_material = _map_mesh.get_active_material(0) + var map_material = _map_mesh_instance.get_active_material(0) if map_material == null: push_error("Map mesh is missing material!") return @@ -87,11 +86,8 @@ func _unhandled_input(event : InputEvent): # Check if the mouse is outside of bounds var mouse_inside_flag := 0 < _mouse_pos_map.x and _mouse_pos_map.x < 1 and 0 < _mouse_pos_map.y and _mouse_pos_map.y < 1 if mouse_inside_flag: - var mouse_pos2D := _mouse_pos_map - mouse_pos2D.x = mouse_pos2D.x * 2.0 - 0.5 - mouse_pos2D *= Vector2(_map_province_shape_image.get_size()) - - var province_colour := _map_province_shape_image.get_pixelv(Vector2i(mouse_pos2D)).to_argb32() & 0xFFFFFF + var mouse_pixel_pos := Vector2i(_mouse_pos_map * Vector2(_map_province_shape_image.get_size())) + var province_colour := _map_province_shape_image.get_pixelv(mouse_pixel_pos).to_argb32() & 0xFFFFFF var province_identifier := MapSingleton.get_province_identifier_from_colour(province_colour) _map_shader_material.set_shader_parameter(_shader_param_selected_pos, _mouse_pos_map) province_selected.emit(province_identifier) @@ -142,9 +138,7 @@ func _move_process(delta : float) -> void: _camera.global_translate(move) func _clamp_over_map() -> void: - var left := _map_mesh_corner.x + 0.25 * _map_mesh_dims.x - var longitude := fposmod(_camera.position.x - left, _map_mesh_dims.x * 0.5) - _camera.position.x = left + longitude + _camera.position.x = _map_mesh_corner.x + fposmod(_camera.position.x - _map_mesh_corner.x, _map_mesh_dims.x) _camera.position.z = clamp(_camera.position.z, _map_mesh_corner.y, _map_mesh_corner.y + _map_mesh_dims.y) func _zoom_process(delta : float) -> void: diff --git a/game/src/GameSession/MapView.tscn b/game/src/GameSession/MapView.tscn index 457bc8e..4650acb 100644 --- a/game/src/GameSession/MapView.tscn +++ b/game/src/GameSession/MapView.tscn @@ -11,12 +11,12 @@ shader_parameter/hover_pos = Vector2(0.5, 0.5) shader_parameter/selected_pos = Vector2(0.5, 0.5) shader_parameter/terrain_tex = ExtResource("3_l8pnf") -[sub_resource type="PlaneMesh" id="PlaneMesh_skc48"] +[sub_resource type="MapMesh" id="MapMesh_3gtsd"] -[node name="MapView" type="Node3D" node_paths=PackedStringArray("_camera", "_map_mesh")] +[node name="MapView" type="Node3D" node_paths=PackedStringArray("_camera", "_map_mesh_instance")] script = ExtResource("1_exccw") _camera = NodePath("MapCamera") -_map_mesh = NodePath("MapMeshInstance") +_map_mesh_instance = NodePath("MapMeshInstance") [node name="MapCamera" type="Camera3D" parent="."] transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, 0, 1, 1) @@ -24,4 +24,4 @@ transform = Transform3D(1, 0, 0, 0, 0.707107, 0.707107, 0, -0.707107, 0.707107, [node name="MapMeshInstance" type="MeshInstance3D" parent="."] transform = Transform3D(10, 0, 0, 0, 10, 0, 0, 0, 10, 0, 0, 0) material_override = SubResource("ShaderMaterial_tayeg") -mesh = SubResource("PlaneMesh_skc48") +mesh = SubResource("MapMesh_3gtsd") diff --git a/game/src/GameSession/TerrainMap.gdshader b/game/src/GameSession/TerrainMap.gdshader index f78779d..61b7032 100644 --- a/game/src/GameSession/TerrainMap.gdshader +++ b/game/src/GameSession/TerrainMap.gdshader @@ -11,21 +11,13 @@ uniform vec2 hover_pos; // Position in UV coords of a pixel belonging to the currently selected province uniform vec2 selected_pos; -// Transform map mesh UV coordinates to account for the extra -// half map on either side. This takes the x-coord from [0 -> 1] -// to [-0.5 -> 1.5], while leaving the y-coord unchanged. -vec2 fix_uv(vec2 uv) { - return vec2(uv.x * 2.0 - 0.5, uv.y); -} - const vec3 NULL_COLOUR = vec3(0.0); void fragment() { - vec2 fixed_uv = fix_uv(UV); - vec3 terrain_colour = texture(terrain_tex, fixed_uv).rgb; - vec3 prov_colour = texture(province_tex, fixed_uv).rgb; - vec3 hover_colour = texture(province_tex, fix_uv(hover_pos)).rgb; - vec3 selected_colour = texture(province_tex, fix_uv(selected_pos)).rgb; + vec3 terrain_colour = texture(terrain_tex, UV).rgb; + vec3 prov_colour = texture(province_tex, UV).rgb; + vec3 hover_colour = texture(province_tex, hover_pos).rgb; + vec3 selected_colour = texture(province_tex, selected_pos).rgb; // Boost prov_colour's contribution if it matches hover_colour or selected_colour float mix_val = float(prov_colour == hover_colour) * 0.3 + float(prov_colour == selected_colour) * 0.5; // Set to 0 if the province has NULL colour -- cgit v1.2.3-56-ga3b1