diff --git a/tests/world_time_validation.gd b/tests/world_time_validation.gd index 1b74f7b..666831e 100644 --- a/tests/world_time_validation.gd +++ b/tests/world_time_validation.gd @@ -14,6 +14,7 @@ const FishingSpotType = preload("res://fishing/fishing_spot.gd") const FishableWaterRegionType = preload( "res://world/fishable_water_region.gd" ) +const WeatherIconType = preload("res://ui/weather_icon.gd") const Catalog: FishPoolType = preload("res://fish/pools/fish_catalog.tres") @@ -28,6 +29,7 @@ func _run() -> void: _validate_fishing_spot_context() _validate_network_snapshot_bounds() _validate_environment_presentation() + _validate_weather_clock_icon() print("World time validation: PASS") quit() @@ -154,7 +156,11 @@ func _validate_environment_presentation() -> void: var world_environment := WorldEnvironment.new() var environment := Environment.new() var sky := Sky.new() - sky.sky_material = ProceduralSkyMaterial.new() + var sky_material := ShaderMaterial.new() + sky_material.shader = preload( + "res://world/environment/netfishing_sky.gdshader" + ) + sky.sky_material = sky_material environment.sky = sky environment.adjustment_enabled = true environment.fog_enabled = true @@ -170,13 +176,63 @@ func _validate_environment_presentation() -> void: visuals.apply_time_immediately(12.0) var runtime_environment: Environment = world_environment.environment var runtime_sky_material := ( - runtime_environment.sky.sky_material as ProceduralSkyMaterial + runtime_environment.sky.sky_material as ShaderMaterial + ) + var day_horizon_value: Variant = runtime_sky_material.get_shader_parameter( + "sky_horizon_color" + ) + assert(day_horizon_value is Color) + var day_horizon: Color = day_horizon_value as Color + var day_sun_visibility: float = float( + runtime_sky_material.get_shader_parameter("sun_visibility") + ) + var day_sun_direction: Vector3 = ( + runtime_sky_material.get_shader_parameter("sun_direction") as Vector3 + ) + assert(day_sun_visibility > 0.99) + assert(day_sun_direction.y > 0.85) + assert( + float(runtime_sky_material.get_shader_parameter("moon_visibility")) + < 0.01 ) - var day_horizon: Color = runtime_sky_material.sky_horizon_color var day_ambient_energy: float = runtime_environment.ambient_light_energy visuals.apply_time_immediately(0.0) - assert(runtime_sky_material.sky_horizon_color != day_horizon) + var night_horizon: Color = runtime_sky_material.get_shader_parameter( + "sky_horizon_color" + ) as Color + assert(night_horizon != day_horizon) + assert( + float(runtime_sky_material.get_shader_parameter("sun_visibility")) + < 0.01 + ) + var night_moon_visibility: float = float( + runtime_sky_material.get_shader_parameter("moon_visibility") + ) + var night_moon_direction: Vector3 = ( + runtime_sky_material.get_shader_parameter("moon_direction") as Vector3 + ) + var night_sun_direction: Vector3 = ( + runtime_sky_material.get_shader_parameter("sun_direction") as Vector3 + ) + assert(night_moon_visibility > 0.99) + assert(night_moon_direction.y > 0.85) + assert(night_moon_direction.is_equal_approx(-night_sun_direction)) assert(runtime_environment.ambient_light_energy < day_ambient_energy) visuals.apply_time_immediately(20.0) - assert(runtime_sky_material.sky_horizon_color.r > day_horizon.r) + var dusk_horizon: Color = runtime_sky_material.get_shader_parameter( + "sky_horizon_color" + ) as Color + assert(dusk_horizon.r > day_horizon.r) world_root.queue_free() + + +func _validate_weather_clock_icon() -> void: + var weather_icon := WeatherIconType.new() + root.add_child(weather_icon) + assert(weather_icon.tooltip_text == "clear") + assert(not weather_icon.is_nighttime()) + weather_icon.set_nighttime(true) + assert(weather_icon.is_nighttime()) + weather_icon.set_weather(WorldWeatherService.Weather.CLOUDY) + assert(weather_icon.tooltip_text == "cloudy") + weather_icon.queue_free() diff --git a/tests/world_weather_validation.gd b/tests/world_weather_validation.gd index 40c7754..1238fc4 100644 --- a/tests/world_weather_validation.gd +++ b/tests/world_weather_validation.gd @@ -146,7 +146,11 @@ func _validate_weather_presentation() -> void: var world_environment := WorldEnvironment.new() var environment := Environment.new() var sky := Sky.new() - sky.sky_material = ProceduralSkyMaterial.new() + var sky_material := ShaderMaterial.new() + sky_material.shader = preload( + "res://world/environment/netfishing_sky.gdshader" + ) + sky.sky_material = sky_material environment.sky = sky environment.adjustment_enabled = true environment.fog_enabled = true diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index 1ccaa00..fce081a 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -958,6 +958,8 @@ func _on_world_time_changed( ) -> void: if _clock_label != null and _world_time != null: _clock_label.text = _world_time.get_clock_text() + if _weather_icon != null and _world_time != null: + _weather_icon.set_nighttime(_world_time.is_night_period()) func _on_world_weather_changed( diff --git a/ui/weather_icon.gd b/ui/weather_icon.gd index 2b8ed6e..894e140 100644 --- a/ui/weather_icon.gd +++ b/ui/weather_icon.gd @@ -4,27 +4,41 @@ extends Control const BUBBLE_COLOR := Color(0.025, 0.13, 0.19, 0.94) const ICON_COLOR := Color(0.78, 0.91, 0.95) const SUN_COLOR := Color(0.98, 0.82, 0.34) +const MOON_COLOR := Color(0.78, 0.88, 1.0) const RAIN_COLOR := Color(0.28, 0.73, 0.82) var _weather: WorldWeatherService.Weather = WorldWeatherService.Weather.SUNNY +var _is_nighttime: bool = false func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_PASS focus_mode = Control.FOCUS_NONE custom_minimum_size = Vector2(34.0, 34.0) - tooltip_text = "sunny" + _update_tooltip() queue_redraw() func set_weather(weather: WorldWeatherService.Weather) -> void: if _weather == weather: + _update_tooltip() return _weather = weather - tooltip_text = WorldWeatherService.weather_name(_weather) + _update_tooltip() queue_redraw() +func set_nighttime(is_nighttime: bool) -> void: + if _is_nighttime == is_nighttime: + return + _is_nighttime = is_nighttime + queue_redraw() + + +func is_nighttime() -> bool: + return _is_nighttime + + func get_weather() -> WorldWeatherService.Weather: return _weather @@ -35,7 +49,10 @@ func _draw() -> void: draw_circle(center, radius, BUBBLE_COLOR) match _weather: WorldWeatherService.Weather.SUNNY: - _draw_sun(center) + if _is_nighttime: + _draw_moon(center) + else: + _draw_sun(center) WorldWeatherService.Weather.CLOUDY: _draw_cloud(center + Vector2(0.0, 1.0)) WorldWeatherService.Weather.RAINY: @@ -73,6 +90,19 @@ func _draw_sun(center: Vector2) -> void: ) +func _draw_moon(center: Vector2) -> void: + draw_circle(center, 8.0, MOON_COLOR) + draw_circle(center + Vector2(4.0, -2.0), 7.0, BUBBLE_COLOR) + + +func _update_tooltip() -> void: + tooltip_text = ( + "clear" + if _weather == WorldWeatherService.Weather.SUNNY + else WorldWeatherService.weather_name(_weather) + ) + + func _draw_cloud(center: Vector2) -> void: draw_circle(center + Vector2(-5.0, 0.0), 5.0, ICON_COLOR) draw_circle(center + Vector2(0.0, -3.0), 6.0, ICON_COLOR) diff --git a/world/environment/netfishing_sky.gdshader b/world/environment/netfishing_sky.gdshader new file mode 100644 index 0000000..b70ab79 --- /dev/null +++ b/world/environment/netfishing_sky.gdshader @@ -0,0 +1,83 @@ +shader_type sky; + +uniform vec4 sky_top_color : source_color = vec4(0.204, 0.498, 0.643, 1.0); +uniform vec4 sky_horizon_color : source_color = vec4(0.663, 0.843, 0.847, 1.0); +uniform vec4 ground_bottom_color : source_color = vec4(0.157, 0.361, 0.439, 1.0); +uniform vec4 ground_horizon_color : source_color = vec4(0.549, 0.749, 0.765, 1.0); + +uniform vec3 sun_direction = vec3(0.0, 1.0, 0.0); +uniform vec4 sun_color : source_color = vec4(1.0, 0.86, 0.46, 1.0); +uniform float sun_visibility : hint_range(0.0, 1.0) = 1.0; +uniform float sun_radius : hint_range(0.005, 0.15) = 0.055; +uniform float sun_edge_softness : hint_range(0.001, 0.03) = 0.003; +uniform float sun_halo_radius : hint_range(0.02, 0.35) = 0.14; +uniform float sun_halo_strength : hint_range(0.0, 1.0) = 0.16; + +uniform vec3 moon_direction = vec3(0.0, -1.0, 0.0); +uniform vec4 moon_color : source_color = vec4(0.78, 0.88, 1.0, 1.0); +uniform float moon_visibility : hint_range(0.0, 1.0) = 0.0; +uniform float moon_radius : hint_range(0.005, 0.15) = 0.045; +uniform float moon_edge_softness : hint_range(0.001, 0.03) = 0.003; +uniform float moon_halo_radius : hint_range(0.02, 0.35) = 0.11; +uniform float moon_halo_strength : hint_range(0.0, 1.0) = 0.12; + + +void sky() { + vec3 view_direction = normalize(EYEDIR); + float above_horizon = max(view_direction.y, 0.0); + float below_horizon = max(-view_direction.y, 0.0); + vec3 sky_color = mix( + sky_horizon_color.rgb, + sky_top_color.rgb, + pow(above_horizon, 0.72) + ); + vec3 ground_color = mix( + ground_horizon_color.rgb, + ground_bottom_color.rgb, + pow(below_horizon, 0.16) + ); + vec3 base_color = mix( + ground_color, + sky_color, + smoothstep(-0.01, 0.01, view_direction.y) + ); + + float alignment = dot(view_direction, normalize(sun_direction)); + float disc_outer = cos(sun_radius + sun_edge_softness); + float disc_inner = cos(sun_radius); + float halo_outer = cos(sun_halo_radius); + float sun_disc = smoothstep(disc_outer, disc_inner, alignment); + float sun_halo = smoothstep(halo_outer, disc_outer, alignment); + float horizon_visibility = smoothstep(-0.035, 0.02, sun_direction.y); + float visible_strength = sun_visibility * horizon_visibility; + base_color += sun_color.rgb * sun_halo * sun_halo_strength * visible_strength; + base_color = mix(base_color, sun_color.rgb, sun_disc * visible_strength); + + float moon_alignment = dot(view_direction, normalize(moon_direction)); + float moon_disc_outer = cos(moon_radius + moon_edge_softness); + float moon_disc_inner = cos(moon_radius); + float moon_halo_outer = cos(moon_halo_radius); + float moon_disc = smoothstep( + moon_disc_outer, moon_disc_inner, moon_alignment + ); + float moon_halo = smoothstep( + moon_halo_outer, moon_disc_outer, moon_alignment + ); + float moon_horizon_visibility = smoothstep( + -0.035, 0.02, moon_direction.y + ); + float moon_visible_strength = ( + moon_visibility * moon_horizon_visibility + ); + base_color += ( + moon_color.rgb + * moon_halo + * moon_halo_strength + * moon_visible_strength + ); + base_color = mix( + base_color, moon_color.rgb, moon_disc * moon_visible_strength + ); + + COLOR = base_color; +} diff --git a/world/environment/netfishing_sky.gdshader.uid b/world/environment/netfishing_sky.gdshader.uid new file mode 100644 index 0000000..784558b --- /dev/null +++ b/world/environment/netfishing_sky.gdshader.uid @@ -0,0 +1 @@ +uid://chbcdlhui3ro4 diff --git a/world/environment/netfishing_sky.tres b/world/environment/netfishing_sky.tres index 1b43a5e..3661dee 100644 --- a/world/environment/netfishing_sky.tres +++ b/world/environment/netfishing_sky.tres @@ -1,13 +1,9 @@ -[gd_resource type="Sky" format=3 uid="uid://dersyay71dyat"] +[gd_resource type="Sky" load_steps=3 format=3 uid="uid://dersyay71dyat"] -[sub_resource type="ProceduralSkyMaterial" id="ProceduralSkyMaterial_netfishing"] -sky_top_color = Color(0.203922, 0.498039, 0.643137, 1) -sky_horizon_color = Color(0.662745, 0.843137, 0.847059, 1) -ground_bottom_color = Color(0.156863, 0.360784, 0.439216, 1) -ground_horizon_color = Color(0.54902, 0.74902, 0.764706, 1) -ground_curve = 0.08 -sun_angle_max = 18.0 -sun_curve = 0.22 +[ext_resource type="Shader" path="res://world/environment/netfishing_sky.gdshader" id="1_sky_shader"] + +[sub_resource type="ShaderMaterial" id="ShaderMaterial_netfishing"] +shader = ExtResource("1_sky_shader") [resource] -sky_material = SubResource("ProceduralSkyMaterial_netfishing") +sky_material = SubResource("ShaderMaterial_netfishing") diff --git a/world/world_time_visual_controller.gd b/world/world_time_visual_controller.gd index 6edd306..611e60b 100644 --- a/world/world_time_visual_controller.gd +++ b/world/world_time_visual_controller.gd @@ -13,6 +13,7 @@ const DAY_GROUND_HORIZON := Color(0.549, 0.749, 0.765) const DAY_AMBIENT := Color(0.82, 0.90, 0.92) const DAY_FOG := Color(0.549, 0.749, 0.765) const DAY_SUN := Color(0.88, 0.94, 0.96) +const DAY_SUN_DISC := Color(1.0, 0.86, 0.46) const NIGHT_SKY_TOP := Color(0.026, 0.050, 0.105) const NIGHT_SKY_HORIZON := Color(0.10, 0.17, 0.28) @@ -29,6 +30,8 @@ const WARM_GROUND_HORIZON := Color(0.82, 0.34, 0.18) const WARM_AMBIENT := Color(0.92, 0.58, 0.40) const WARM_FOG := Color(0.74, 0.39, 0.27) const WARM_SUN := Color(1.0, 0.58, 0.30) +const WARM_SUN_DISC := Color(1.0, 0.45, 0.16) +const MOON_DISC := Color(0.78, 0.88, 1.0) const CLOUDY_TINT := Color(0.53, 0.61, 0.66) const RAINY_TINT := Color(0.31, 0.42, 0.50) @@ -40,7 +43,7 @@ var _world_environment: WorldEnvironment var _sun: DirectionalLight3D var _rain_target: Node3D var _environment: Environment -var _sky_material: ProceduralSkyMaterial +var _sky_material: ShaderMaterial var _rain: GPUParticles3D var _elapsed: float = 0.0 var _weather_from: WorldWeatherService.Weather = ( @@ -125,13 +128,13 @@ func _prepare_runtime_environment() -> bool: return false var runtime_sky: Sky = _environment.sky.duplicate(true) as Sky if runtime_sky == null or runtime_sky.sky_material == null: - push_error("World time visuals require a procedural sky material.") + push_error("World time visuals require a sky material.") return false _sky_material = ( - runtime_sky.sky_material.duplicate(true) as ProceduralSkyMaterial + runtime_sky.sky_material.duplicate(true) as ShaderMaterial ) if _sky_material == null: - push_error("World time visuals require a ProceduralSkyMaterial.") + push_error("World time visuals require the NETfishing sky material.") return false runtime_sky.sky_material = _sky_material _environment.sky = runtime_sky @@ -213,15 +216,19 @@ func _apply_time(time_hours: float) -> void: var tint_strength: float = _weather_value( 0.0, 0.30, 0.48, 0.62 ) - _sky_material.sky_top_color = sky_top.lerp(weather_tint, tint_strength) - _sky_material.sky_horizon_color = sky_horizon.lerp( - weather_tint, tint_strength + _sky_material.set_shader_parameter( + "sky_top_color", sky_top.lerp(weather_tint, tint_strength) ) - _sky_material.ground_bottom_color = ground_bottom.lerp( - weather_tint, tint_strength * 0.62 + _sky_material.set_shader_parameter( + "sky_horizon_color", sky_horizon.lerp(weather_tint, tint_strength) ) - _sky_material.ground_horizon_color = ground_horizon.lerp( - weather_tint, tint_strength * 0.76 + _sky_material.set_shader_parameter( + "ground_bottom_color", + ground_bottom.lerp(weather_tint, tint_strength * 0.62), + ) + _sky_material.set_shader_parameter( + "ground_horizon_color", + ground_horizon.lerp(weather_tint, tint_strength * 0.76), ) _environment.background_energy_multiplier = ( lerpf(0.52, 0.85, daylight) @@ -269,6 +276,24 @@ func _apply_time(time_hours: float) -> void: SUN_YAW_DEGREES, 0.0, ) + _sky_material.set_shader_parameter( + "sun_direction", _sun.global_transform.basis.z.normalized() + ) + _sky_material.set_shader_parameter( + "sun_color", DAY_SUN_DISC.lerp(WARM_SUN_DISC, warmth) + ) + _sky_material.set_shader_parameter( + "sun_visibility", + daylight * _weather_value(1.0, 0.65, 0.28, 0.15), + ) + _sky_material.set_shader_parameter( + "moon_direction", -_sun.global_transform.basis.z.normalized() + ) + _sky_material.set_shader_parameter("moon_color", MOON_DISC) + _sky_material.set_shader_parameter( + "moon_visibility", + (1.0 - daylight) * _weather_value(1.0, 0.72, 0.36, 0.20), + ) _sun.light_color = _blended_color( NIGHT_SUN, DAY_SUN, WARM_SUN, daylight, warmth )