From 8ceb6cb16b3e7bbd2e8a952cacedfa1393e638c9 Mon Sep 17 00:00:00 2001 From: Overside srl Date: Sun, 10 May 2026 18:44:43 +0200 Subject: [PATCH] fix river water direction2 --- core/biome_generator/biome_generator.gd | 114 ++++++++++++++++++++++++ core/daynight/water_river.gdshader | 45 ++++++++-- 2 files changed, 151 insertions(+), 8 deletions(-) diff --git a/core/biome_generator/biome_generator.gd b/core/biome_generator/biome_generator.gd index 2c72a7f..5bd7c45 100644 --- a/core/biome_generator/biome_generator.gd +++ b/core/biome_generator/biome_generator.gd @@ -8,6 +8,19 @@ const CHUNK_GENERATION_STEPS_PER_FRAME: int = 2 const CHUNK_CLEANUP_STEPS_PER_FRAME: int = 4 const LAMPPOST_WIRE_STEPS_PER_FRAME: int = 1 const RAILWAY_SCENE_DIRECTORY: String = "res://tgcc/chunk/railway/scene" +const RIVER_SIDE_ORDER: Array[String] = ["north", "est", "south", "west"] +const RIVER_DIRECTIONS: Dictionary = { + "north": Vector2(0.0, -1.0), + "est": Vector2(1.0, 0.0), + "south": Vector2(0.0, 1.0), + "west": Vector2(-1.0, 0.0), +} +const RIVER_NEIGHBOUR_OFFSETS: Dictionary = { + "north": Vector2i(0, -1), + "est": Vector2i(1, 0), + "south": Vector2i(0, 1), + "west": Vector2i(-1, 0), +} @export_group("Rails") @export var rail_path: Path3D @@ -616,11 +629,15 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: var have_lamppost = false if info_new != null and "have_lamppost" in info_new: have_lamppost = info_new.have_lamppost + + var river_flow_direction = _calculate_river_flow_direction(grid_pos, choise.data["river_connections"]) + _apply_river_flow_direction(new_chunk, river_flow_direction) board[grid_pos] = { "type": "bioma", "exit": choise.data["connections"], "river_exit": choise.data["river_connections"], + "river_flow_direction": river_flow_direction, "heights": choise.data["heights"], "node": new_chunk, "info": info_new, @@ -647,12 +664,109 @@ func _add_compatible_biome(grid_pos: Vector2i) -> void: "type": "biome", "exit": {"north":false, "est":false, "south":false, "west":false}, "river_exit": {"north":false, "est":false, "south":false, "west":false}, + "river_flow_direction": Vector2.ZERO, "heights": safe_heights, "node": backup, "info": info_backup, "have_lamppost": false } +func _calculate_river_flow_direction(grid_pos: Vector2i, river_connections: Dictionary) -> Vector2: + var connected_sides: Array[String] = [] + for side in RIVER_SIDE_ORDER: + if river_connections.has(side) and river_connections[side]: + connected_sides.append(side) + + if connected_sides.is_empty(): + return Vector2.ZERO + + var neighbour_flow = _get_connected_neighbour_river_flow(grid_pos, connected_sides) + if not neighbour_flow.is_zero_approx(): + var continued_flow = _continue_river_flow_from_neighbour(grid_pos, connected_sides, neighbour_flow) + if not continued_flow.is_zero_approx(): + return continued_flow.normalized() + + var default_flow = _get_default_river_flow(connected_sides) + if default_flow.is_zero_approx(): + return Vector2.ZERO + return default_flow.normalized() + +func _get_connected_neighbour_river_flow(grid_pos: Vector2i, connected_sides: Array[String]) -> Vector2: + for side in connected_sides: + var neighbour_pos: Vector2i = grid_pos + RIVER_NEIGHBOUR_OFFSETS[side] + if not board.has(neighbour_pos): + continue + var neighbour = board[neighbour_pos] + if not neighbour.has("river_flow_direction"): + continue + var neighbour_flow: Vector2 = neighbour["river_flow_direction"] + if not neighbour_flow.is_zero_approx(): + return neighbour_flow.normalized() + return Vector2.ZERO + +func _continue_river_flow_from_neighbour(grid_pos: Vector2i, connected_sides: Array[String], neighbour_flow: Vector2) -> Vector2: + for side in connected_sides: + var neighbour_pos: Vector2i = grid_pos + RIVER_NEIGHBOUR_OFFSETS[side] + if not board.has(neighbour_pos): + continue + var neighbour = board[neighbour_pos] + if not neighbour.has("river_flow_direction"): + continue + var side_direction: Vector2 = RIVER_DIRECTIONS[side] + var neighbour_direction: Vector2 = neighbour["river_flow_direction"] + if neighbour_direction.is_zero_approx(): + continue + neighbour_direction = neighbour_direction.normalized() + var other_sides = connected_sides.duplicate() + other_sides.erase(side) + if other_sides.is_empty(): + return neighbour_direction + + var other_direction = _get_average_river_side_direction(other_sides) + if neighbour_direction.dot(-side_direction) > 0.25: + return other_direction - side_direction + if neighbour_direction.dot(side_direction) > 0.25: + return side_direction - other_direction + + var default_flow = _get_default_river_flow(connected_sides) + if default_flow.dot(neighbour_flow) < 0.0: + return -default_flow + return default_flow + +func _get_default_river_flow(connected_sides: Array[String]) -> Vector2: + if connected_sides.size() == 1: + return RIVER_DIRECTIONS[connected_sides[0]] + + if connected_sides.has("north") and connected_sides.has("south"): + return Vector2(0.0, 1.0) + if connected_sides.has("est") and connected_sides.has("west"): + return Vector2(1.0, 0.0) + + var start_direction: Vector2 = RIVER_DIRECTIONS[connected_sides[0]] + var end_direction = _get_average_river_side_direction(connected_sides.slice(1)) + return end_direction - start_direction + +func _get_average_river_side_direction(sides: Array[String]) -> Vector2: + var direction := Vector2.ZERO + for side in sides: + direction += RIVER_DIRECTIONS[side] + if direction.is_zero_approx(): + return Vector2.ZERO + return direction / float(sides.size()) + +func _apply_river_flow_direction(root: Node, flow_direction: Vector2) -> void: + if flow_direction.is_zero_approx(): + return + _set_river_flow_direction_recursive(root, flow_direction.normalized()) + +func _set_river_flow_direction_recursive(node: Node, flow_direction: Vector2) -> void: + if node is MeshInstance3D and node.name.begins_with("Water_F"): + var mesh_instance := node as MeshInstance3D + mesh_instance.set_instance_shader_parameter("river_flow_direction", flow_direction) + + for child in node.get_children(): + _set_river_flow_direction_recursive(child, flow_direction) + func _needed_connection(near_pos: Vector2i, side_needed: String) -> int: if not board.has(near_pos): _register_cell_with_ray(near_pos) diff --git a/core/daynight/water_river.gdshader b/core/daynight/water_river.gdshader index 19ac015..2b2f015 100644 --- a/core/daynight/water_river.gdshader +++ b/core/daynight/water_river.gdshader @@ -32,9 +32,6 @@ uniform float foam_roughness : hint_range(0.0, 1.0, 0.01) = 0.05; group_uniforms Wave; uniform sampler2D wave_texture; -#if !USE_UNSHADED - uniform sampler2D wave_normal_texture; -#endif uniform float wave_softness : hint_range(0.0, 10.0, 0.1) = 3.0; uniform vec2 wave_scale = vec2(0.2); uniform vec2 wave_layer_scale = vec2(1.5); @@ -42,6 +39,7 @@ uniform float wave_highlight : hint_range(0.0, 1.0, 0.05) = 0.5; group_uniforms Wave.Motion; uniform vec2 wave_velocity = vec2(0.02); +instance uniform vec2 river_flow_direction = vec2(0.0, 0.0); group_uniforms Foam; uniform sampler2D foam_texture; @@ -72,6 +70,7 @@ uniform float foam_exponent = 2.0; uniform sampler2D screen_texture : hint_screen_texture; varying vec3 world_pos; +varying vec2 world_wave_velocity; #if USE_CAUSTICS vec3 sample_caustics(vec2 uv){ @@ -103,17 +102,47 @@ vec4 sample_wave(sampler2D tex, vec2 uv, vec2 velocity, float lod){ return wave2; } +vec3 sample_wave_normal(vec2 uv, vec2 velocity, float center_wave) { + vec2 normal_offset = vec2(0.25, 0.0); + float wave_x = sample_wave(wave_texture, uv + normal_offset.xy, velocity, wave_softness).r; + float wave_z = sample_wave(wave_texture, uv + normal_offset.yx, velocity, wave_softness).r; + vec2 slope = vec2(center_wave - wave_x, center_wave - wave_z) * 0.35; + return normalize(vec3(slope, 1.0)) * 0.5 + 0.5; +} + +vec2 get_world_wave_velocity(mat4 model_matrix) { + float velocity_length = length(wave_velocity); + if (velocity_length <= 0.0001) { + return vec2(0.0); + } + + float flow_direction_length = length(river_flow_direction); + if (flow_direction_length > 0.0001) { + return (river_flow_direction / flow_direction_length) * velocity_length; + } + + vec3 world_direction_3d = (model_matrix * vec4(0.0, 1.0, 0.0, 0.0)).xyz; + vec2 world_direction = world_direction_3d.xz; + float world_direction_length = length(world_direction); + if (world_direction_length <= 0.0001) { + return wave_velocity; + } + + return (world_direction / world_direction_length) * velocity_length; +} + void vertex(){ world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; + world_wave_velocity = get_world_wave_velocity(MODEL_MATRIX); #if USE_DISPLACEMENT - float wave = sample_wave(wave_texture, world_pos.xz, wave_velocity, wave_softness).r; + float wave = sample_wave(wave_texture, world_pos.xz, world_wave_velocity, wave_softness).r; VERTEX.y += wave*displacement_amount; #endif } void fragment() { - float wave = sample_wave(wave_texture, world_pos.xz, wave_velocity, wave_softness).r; + float wave = sample_wave(wave_texture, world_pos.xz, world_wave_velocity, wave_softness).r; wave = smoothstep(0.0,1.0,wave); vec2 screen_uv = SCREEN_UV; @@ -141,8 +170,8 @@ void fragment() { // Caustics #if USE_CAUSTICS - vec3 caustics1 = sample_caustics(surface_uv + (TIME * -wave_velocity)); - vec3 caustics2 = sample_caustics((surface_uv + (caustics1.r*0.05)) + (TIME * (wave_velocity*0.5))); + vec3 caustics1 = sample_caustics(surface_uv + (TIME * -world_wave_velocity)); + vec3 caustics2 = sample_caustics((surface_uv + (caustics1.r*0.05)) + (TIME * (world_wave_velocity*0.5))); vec3 caustics = caustics2 * (1.0 - depth); #endif @@ -170,7 +199,7 @@ void fragment() { color = mix(color, foam_color.rgb, foam); #if !USE_UNSHADED - vec3 wave_normal_map = sample_wave(wave_normal_texture, world_pos.xz, wave_velocity, wave_softness).rgb; + vec3 wave_normal_map = sample_wave_normal(world_pos.xz, world_wave_velocity, wave); NORMAL_MAP = wave_normal_map; #endif