diff --git a/core/biome_generator/rails.gd b/core/biome_generator/rails.gd index 1400017..c90775b 100644 --- a/core/biome_generator/rails.gd +++ b/core/biome_generator/rails.gd @@ -47,15 +47,112 @@ var next_stop_index: int = 0 var stop_multiply: float = 1.0 var is_restarting: bool = false var tween_restart: Tween +var environment_config: EnvironmentConfig func _ready() -> void: + _cache_environment_config() if curve != null and curve.get_baked_length() > 0: build_rails() build_train() _plan_stops() + _apply_initial_train_start() else: print("WARNING: Draw Path3D for rails!") +func _cache_environment_config() -> void: + var parent_node: Node = get_parent() + if parent_node != null: + var day_night_node := parent_node.get_node_or_null("DayNight") as EnvironmentManagerRoot + if day_night_node != null: + environment_config = day_night_node.environment_config + return + + var current_scene: Node = get_tree().current_scene + if current_scene == null: + return + + for child in current_scene.get_children(): + var environment_manager := child as EnvironmentManagerRoot + if environment_manager != null: + environment_config = environment_manager.environment_config + return + +func _apply_initial_train_start() -> void: + if train_instance == null or curve == null: + return + + var total_length: float = curve.get_baked_length() + if total_length <= 0.0: + return + + #start from a random position + if environment_config != null and environment_config.train_start_from_random_position: + train_progress = randf() * total_length + _update_next_stop_index_from_progress() + elif environment_config != null and not stop_offset.is_empty(): + #start from a specific stop + var stop_index: int = clampi(environment_config.train_start_stop_index, 0, stop_offset.size() - 1) + train_progress = stop_offset[stop_index] + _set_next_stop_index_from_current(stop_index) + else: + train_progress = wrapf(train_progress, 0.0, total_length) + _update_next_stop_index_from_progress() + + _snap_train_to_progress() + +func _set_next_stop_index_from_current(current_stop_index: int) -> void: + if stop_offset.is_empty(): + next_stop_index = 0 + return + + if train_speed >= 0.0: + next_stop_index = (current_stop_index + 1) % stop_offset.size() + else: + next_stop_index = current_stop_index - 1 + if next_stop_index < 0: + next_stop_index = stop_offset.size() - 1 + +func _update_next_stop_index_from_progress() -> void: + if stop_offset.is_empty(): + next_stop_index = 0 + return + + if train_speed >= 0.0: + next_stop_index = 0 + for i in range(stop_offset.size()): + if stop_offset[i] > train_progress + 0.001: + next_stop_index = i + return + return + + next_stop_index = stop_offset.size() - 1 + for i in range(stop_offset.size() - 1, -1, -1): + if stop_offset[i] < train_progress - 0.001: + next_stop_index = i + return + +func _snap_train_to_progress() -> void: + if train_instance == null or curve == null: + return + + var total_length: float = curve.get_baked_length() + if total_length <= 0.0: + return + + train_progress = wrapf(train_progress, 0.0, total_length) + var center_position: Vector3 = to_global(curve.sample_baked(train_progress, true)) + var prog_forward: float = wrapf(train_progress + 2.0, 0.0, total_length) + var forward_position: Vector3 = to_global(curve.sample_baked(prog_forward, true)) + + train_instance.global_position = center_position + if center_position.distance_to(forward_position) > 0.01: + train_instance.look_at(forward_position, Vector3.UP) + train_instance.rotate_y(PI) + + if cameras: + cameras.global_position = center_position + cameras.global_basis = train_instance.global_basis + func build_rails() -> void: var mat_wood = StandardMaterial3D.new() mat_wood.albedo_color = Color(0.35, 0.2, 0.1) diff --git a/core/daynight/weather_controller.gd b/core/daynight/weather_controller.gd index 870cb8b..2573324 100644 --- a/core/daynight/weather_controller.gd +++ b/core/daynight/weather_controller.gd @@ -700,6 +700,7 @@ func init_snow(value: float = 0.0): RenderingServer.global_shader_parameter_set("global_snow_accumulation_speed", environment_config.snow_accumulation_speed) RenderingServer.global_shader_parameter_set("global_snow_melt_speed", environment_config.snow_melt_speed) RenderingServer.global_shader_parameter_set("global_snow_max_accumulation", environment_config.snow_max_accumulation) + RenderingServer.global_shader_parameter_set("global_snow_cap_enabled", 1.0 if environment_config.show_snow_accumulation_volume else 0.0) RenderingServer.global_shader_parameter_set("global_snow_cap_height", environment_config.snow_cap_height) RenderingServer.global_shader_parameter_set("global_snow_cap_flatness_start", environment_config.snow_cap_flatness_start) RenderingServer.global_shader_parameter_set("global_snow_cap_flatness_end", environment_config.snow_cap_flatness_end) diff --git a/core/daynight/weather_overlay.gdshader b/core/daynight/weather_overlay.gdshader index c432cfa..5afa822 100644 --- a/core/daynight/weather_overlay.gdshader +++ b/core/daynight/weather_overlay.gdshader @@ -9,6 +9,7 @@ global uniform float global_snow_melt_speed = 0.1; global uniform float global_snow_threshold = 0.5; global uniform vec4 global_snow_color = vec4(0.92, 0.96, 1.0, 1.0); global uniform float global_snow_max_accumulation = 0.72; +global uniform float global_snow_cap_enabled = 1.0; global uniform float global_snow_cap_height = 0.06; global uniform float global_snow_cap_flatness_start = 0.72; global uniform float global_snow_cap_flatness_end = 0.96; @@ -92,6 +93,7 @@ void vertex() { float snow_amount = get_snow_amount(); vec3 world_pos = (MODEL_MATRIX * vec4(VERTEX, 1.0)).xyz; vec3 world_normal = normalize((MODEL_MATRIX * vec4(NORMAL, 0.0)).xyz); + float snow_cap_enabled = step(0.5, global_snow_cap_enabled); float flat_surface = smoothstep( max(global_snow_threshold, global_snow_cap_flatness_start), @@ -103,7 +105,7 @@ void vertex() { float cap_variation = mix(1.0 - global_snow_cap_noise_strength, 1.0 + global_snow_cap_noise_strength, cap_noise); v_snow_amount = snow_amount; - v_snow_cap_mask = flat_surface * accumulation_growth * cap_variation; + v_snow_cap_mask = flat_surface * accumulation_growth * cap_variation * snow_cap_enabled; VERTEX += NORMAL * (global_snow_cap_height * snow_amount * v_snow_cap_mask); } diff --git a/core/environment_config.gd b/core/environment_config.gd index ac0793b..8a7c045 100644 --- a/core/environment_config.gd +++ b/core/environment_config.gd @@ -133,6 +133,11 @@ extends Resource @export var godray_spawn_height: float = 5.0 #Y world position where god rays spawn @export var godray_scale: Vector3 = Vector3(2.0, 6.0, 2.0) #Scale of the god ray mesh +#Train start settings +@export_group("Train Start") +@export var train_start_from_random_position: bool = false #When enabled the train starts from a random offset on the rail curve instead of a stop +@export_range(0, 64, 1, "or_greater") var train_start_stop_index: int = 1 #Stop index used for the train start when random start is disabled + #Snow settings @export_group("Snow") @export var snow_amount: float = 0.0 #Initial snow coverage amount (0.0 = none, 1.0 = full) @@ -141,6 +146,7 @@ extends Resource @export var snow_threshold: float = 0.4 #Normal Y threshold for snow accumulation on surfaces @export var snow_accumulation_speed: float = 0.014 #Accumulation speed: 0.1 -> 10s, 0.05 -> 20s, 0.02 -> 50s, 0.012 -> ~83s @export var snow_melt_speed: float = 0.05 #Speed at which accumulated snow melts away +@export var show_snow_accumulation_volume: bool = true #Enables vertex snow buildup; when false snow only changes surface color @export var snow_max_accumulation: float = 0.25 #Maximum accumulated snow factor applied to coverage and thickness @export var snow_color: Color = Color(0.9, 0.95, 1.0) #Albedo color of snow on surfaces @export var snow_cap_height: float = 0.03 #Maximum snow thickness extruded on flat surfaces diff --git a/project.godot b/project.godot index 368d02b..09296ab 100644 --- a/project.godot +++ b/project.godot @@ -136,3 +136,7 @@ global_snow_max_accumulation={ "type": "float", "value": 0.0 } +global_snow_cap_enabled={ +"type": "bool", +"value": false +}