308 lines
10 KiB
GDScript
308 lines
10 KiB
GDScript
class_name EnvironmentManagerRoot
|
|
extends Node3D
|
|
|
|
const NOISE_TEXTURE: Texture2D = preload("res://core/daynight/noise.tres")
|
|
const WEATHER_SHADER: Material = preload("res://core/daynight/weather_overlay.tres")
|
|
const DYNAMIC_ENVIRONMENT_UPDATES_PER_FRAME: int = 32 #how many update of the environment (apply materials) will be done per frame
|
|
|
|
@export var environment_config: EnvironmentConfig
|
|
|
|
@export_group("Camera")
|
|
@export var camera: Camera3D
|
|
@export var camera_pivot: Node3D
|
|
|
|
@export_group("Particles")
|
|
@export var particles_wind: GPUParticles3D
|
|
@export var particles_snow: GPUParticles3D
|
|
@export var particles_fireflies: GPUParticles3D
|
|
@export var particles_rain: GPUParticles3D
|
|
@export var godray: PackedScene
|
|
@export var environment_dust: ColorRect
|
|
@export var blur: ColorRect
|
|
@export var environment_shadows: MeshInstance3D
|
|
@export var fog: Node3D
|
|
|
|
@export_group("Sound")
|
|
@export var thunder_sounds: Array[AudioStream]
|
|
@export var rain_sounds: Array[AudioStream]
|
|
|
|
@export_group("Clouds")
|
|
@export var cloud_material: ShaderMaterial
|
|
|
|
#environment nodes
|
|
@onready var sun: DirectionalLight3D = $"../DirectionalLight3D"
|
|
@onready var environment: WorldEnvironment = $"../WorldEnvironment"
|
|
|
|
var day_night_controller: DayNightController
|
|
var weather_controller: WeatherController
|
|
|
|
var day_tween: Tween
|
|
var day_time: float = 0.0
|
|
var pending_environment_nodes: Dictionary = {}
|
|
var weather_shader_no_noise: Material
|
|
|
|
func _ready() -> void:
|
|
|
|
#connect shader when new node is added
|
|
get_tree().node_added.connect(_on_tree_node_added)
|
|
|
|
#set noise texture 2d to materials in special group
|
|
ApplyWindNoiseToMaterials()
|
|
#set weather overlay (snow + rain) to materials in special group
|
|
ApplyWeatherShaderToMaterials()
|
|
|
|
if day_night_controller == null:
|
|
day_night_controller = DayNightController.new(
|
|
environment_config
|
|
)
|
|
day_night_controller.name = "DayNightController"
|
|
day_night_controller.time_changed.connect(select_day_time)
|
|
add_child(day_night_controller)
|
|
|
|
if weather_controller == null:
|
|
weather_controller = WeatherController.new(
|
|
particles_wind,
|
|
particles_snow,
|
|
particles_fireflies,
|
|
particles_rain,
|
|
godray,
|
|
environment_dust,
|
|
blur,
|
|
fog,
|
|
environment_shadows,
|
|
sun,
|
|
environment,
|
|
environment_config,
|
|
camera,
|
|
camera_pivot,
|
|
thunder_sounds,
|
|
rain_sounds
|
|
)
|
|
weather_controller.name = "WeatherController"
|
|
add_child(weather_controller)
|
|
|
|
if day_night_controller != null:
|
|
select_day_time(day_night_controller.current_time)
|
|
|
|
func _process(_delta: float) -> void:
|
|
_process_pending_environment_nodes()
|
|
|
|
func flush_pending_environment_nodes() -> void:
|
|
_process_pending_environment_nodes(-1)
|
|
|
|
func _on_tree_node_added(node: Node) -> void:
|
|
if node.is_in_group("wind_node") or node.is_in_group("weather_node") or node.is_in_group("weather_vegetables_node"):
|
|
_queue_dynamic_environment_node(node)
|
|
|
|
func _queue_dynamic_environment_node(node: Node) -> void:
|
|
if not is_instance_valid(node):
|
|
return
|
|
|
|
var ancestor = node.get_parent()
|
|
while ancestor != null:
|
|
if pending_environment_nodes.has(ancestor.get_instance_id()):
|
|
return
|
|
ancestor = ancestor.get_parent()
|
|
|
|
var stale_ids: Array[int] = []
|
|
for node_id in pending_environment_nodes.keys():
|
|
var pending_node = pending_environment_nodes[node_id]
|
|
if not is_instance_valid(pending_node):
|
|
stale_ids.append(node_id)
|
|
continue
|
|
if node.is_ancestor_of(pending_node):
|
|
stale_ids.append(node_id)
|
|
|
|
for node_id in stale_ids:
|
|
pending_environment_nodes.erase(node_id)
|
|
|
|
pending_environment_nodes[node.get_instance_id()] = node
|
|
|
|
func _process_pending_environment_nodes(max_nodes: int = DYNAMIC_ENVIRONMENT_UPDATES_PER_FRAME) -> void:
|
|
var processed = 0
|
|
for node_id in pending_environment_nodes.keys():
|
|
if max_nodes >= 0 and processed >= max_nodes:
|
|
break
|
|
|
|
var node = pending_environment_nodes[node_id]
|
|
pending_environment_nodes.erase(node_id)
|
|
if not is_instance_valid(node):
|
|
continue
|
|
|
|
_apply_dynamic_environment_materials(node)
|
|
processed += 1
|
|
|
|
func _apply_dynamic_environment_materials(node: Node) -> void:
|
|
if not is_instance_valid(node):
|
|
return
|
|
|
|
if node.is_in_group("wind_node"):
|
|
_apply_wind_noise_to_node(node, NOISE_TEXTURE)
|
|
|
|
if node.is_in_group("weather_node") or node.is_in_group("weather_vegetables_node"):
|
|
_apply_weather_overlay_to_node(node, _get_weather_overlay_material(node))
|
|
|
|
if _should_ignore_weather_overlay(node):
|
|
_clear_weather_overlay_from_node(node)
|
|
|
|
func ApplyWindNoiseToMaterials():
|
|
for node in get_tree().get_nodes_in_group("wind_node"):
|
|
_apply_wind_noise_to_node(node, NOISE_TEXTURE)
|
|
|
|
func _apply_wind_noise_to_node(node: Node, noise_tex: Texture2D) -> void:
|
|
if node is GeometryInstance3D:
|
|
var material_override := node.material_override as ShaderMaterial
|
|
if material_override:
|
|
material_override.set_shader_parameter("wind_noise", noise_tex)
|
|
|
|
if node is MeshInstance3D:
|
|
for surface_index in node.get_surface_override_material_count():
|
|
var surface_material := node.get_surface_override_material(surface_index) as ShaderMaterial
|
|
if surface_material:
|
|
surface_material.set_shader_parameter("wind_noise", noise_tex)
|
|
|
|
for child in node.get_children():
|
|
_apply_wind_noise_to_node(child, noise_tex)
|
|
|
|
func ApplyWeatherShaderToMaterials():
|
|
for node in get_tree().get_nodes_in_group("weather_node"):
|
|
_apply_weather_overlay_to_node(node, _get_weather_overlay_material(node))
|
|
|
|
for node in get_tree().get_nodes_in_group("weather_vegetables_node"):
|
|
_apply_weather_overlay_to_node(node, _get_weather_overlay_material(node))
|
|
|
|
func _get_weather_overlay_material(node: Node) -> Material:
|
|
if not node.is_in_group("weather_overlay_no_noise"):
|
|
return WEATHER_SHADER
|
|
|
|
if weather_shader_no_noise == null:
|
|
weather_shader_no_noise = WEATHER_SHADER.duplicate() as Material
|
|
var shader_material := weather_shader_no_noise as ShaderMaterial
|
|
if shader_material:
|
|
shader_material.set_shader_parameter("snow_noise_enabled", false)
|
|
|
|
return weather_shader_no_noise
|
|
|
|
func _apply_weather_overlay_to_node(node: Node, material: Material) -> void:
|
|
if _should_ignore_weather_overlay(node):
|
|
_clear_weather_overlay_from_node(node)
|
|
return
|
|
|
|
if node is GeometryInstance3D:
|
|
if _should_clear_weather_overlay(node) or _geometry_uses_alpha_texture(node):
|
|
node.material_overlay = null
|
|
else:
|
|
node.material_overlay = material
|
|
|
|
for child in node.get_children():
|
|
_apply_weather_overlay_to_node(child, material)
|
|
|
|
func _clear_weather_overlay_from_node(node: Node) -> void:
|
|
if node is GeometryInstance3D:
|
|
node.material_overlay = null
|
|
|
|
for child in node.get_children():
|
|
_clear_weather_overlay_from_node(child)
|
|
|
|
func _geometry_uses_alpha_texture(node: GeometryInstance3D) -> bool:
|
|
var material_override := node.material_override as ShaderMaterial
|
|
if _shader_material_uses_alpha_texture(material_override):
|
|
return true
|
|
|
|
if node is MeshInstance3D:
|
|
for surface_index in node.get_surface_override_material_count():
|
|
var surface_material := node.get_surface_override_material(surface_index) as ShaderMaterial
|
|
if _shader_material_uses_alpha_texture(surface_material):
|
|
return true
|
|
|
|
if node.mesh:
|
|
for surface_index in node.mesh.get_surface_count():
|
|
var mesh_material := node.mesh.surface_get_material(surface_index) as ShaderMaterial
|
|
if _shader_material_uses_alpha_texture(mesh_material):
|
|
return true
|
|
|
|
return false
|
|
|
|
func _shader_material_uses_alpha_texture(material: ShaderMaterial) -> bool:
|
|
if material == null or material.shader == null:
|
|
return false
|
|
|
|
return material.shader.code.find("alpha_texture") != -1
|
|
|
|
func _should_ignore_weather_overlay(node: Node) -> bool:
|
|
return node.is_in_group("weather_overlay_ignore")
|
|
|
|
func _should_clear_weather_overlay(node: Node) -> bool:
|
|
return node.is_in_group("weather_vegetables_node")
|
|
|
|
func select_day_time(normalized_time: float) -> void:
|
|
#set show_day_time_debug = true to show debug on screen
|
|
#normalized_time is a value between 0 and 1; the time of the day is calculate as "normalized_time" * 1440; day_time is the step to pass from sunrise, to day, to sunset, to night
|
|
#e.g.
|
|
#Time normalized_time day_time
|
|
#00:00 0.0000 3.0
|
|
#06:00 0.2500 1.0
|
|
#09:36 0.4000 1.75
|
|
#10:48 0.4500 2.0
|
|
#12:00 0.5000 2.0
|
|
#19:12 0.8000 2.0
|
|
#21:36 0.9000 2.5
|
|
#24:00 1.0000 3.0
|
|
|
|
if normalized_time < environment_config.sunrise:
|
|
#Sunrise: night → morning
|
|
day_time = 3.0 - (normalized_time / environment_config.sunrise) * 2.0 # 3.0 → 1.0
|
|
elif normalized_time < environment_config.day:
|
|
#Morning: morning → afternoon
|
|
var t: float = (normalized_time - environment_config.sunrise) / (environment_config.day - environment_config.sunrise)
|
|
day_time = 1.0 + t # 1.0 → 2.0
|
|
elif normalized_time < environment_config.sunset:
|
|
#Broad daylight
|
|
day_time = 2.0
|
|
elif normalized_time < environment_config.night:
|
|
#Sunset: afternoon → night
|
|
var t: float = (normalized_time - environment_config.sunset) / (environment_config.night - environment_config.sunset)
|
|
day_time = 2.0 + t # 2.0 → 3.0
|
|
else:
|
|
#Night
|
|
day_time = 3.0
|
|
|
|
if weather_controller:
|
|
weather_controller.day_time = day_time
|
|
_update_clouds_lighting(normalized_time)
|
|
|
|
func _update_clouds_lighting(normalized_time: float) -> void:
|
|
if not cloud_material:
|
|
return
|
|
|
|
if normalized_time < environment_config.sunrise:
|
|
# NOTTE / PRE-ALBA (Colori scuri, nessun rim light solare)
|
|
cloud_material.set_shader_parameter("cloud_color", Color("1a1c2e"))
|
|
cloud_material.set_shader_parameter("shadow_color", Color("0d0e17"))
|
|
cloud_material.set_shader_parameter("rim_color", Color("000000"))
|
|
|
|
elif normalized_time < environment_config.day:
|
|
# MATTINA / ALBA (Colori che iniziano a scaldarsi)
|
|
cloud_material.set_shader_parameter("cloud_color", Color("fdf3e7"))
|
|
cloud_material.set_shader_parameter("shadow_color", Color("5c537a"))
|
|
cloud_material.set_shader_parameter("rim_color", Color("ff9e64"))
|
|
|
|
elif normalized_time < environment_config.sunset:
|
|
# PIENO GIORNO (Nuvole bianche brillanti, ombre azzurrate anime)
|
|
cloud_material.set_shader_parameter("cloud_color", Color("ffffff"))
|
|
cloud_material.set_shader_parameter("shadow_color", Color("687fa1"))
|
|
cloud_material.set_shader_parameter("rim_color", Color("fff3cc"))
|
|
|
|
elif normalized_time < environment_config.night:
|
|
# TRAMONTO (Bordo dorato/arancione acceso e ombre violacee)
|
|
cloud_material.set_shader_parameter("cloud_color", Color("fdf3e7"))
|
|
cloud_material.set_shader_parameter("shadow_color", Color("5c537a"))
|
|
cloud_material.set_shader_parameter("rim_color", Color("ff9e64"))
|
|
|
|
else:
|
|
# NOTTE FONDA (Ritorno al buio)
|
|
cloud_material.set_shader_parameter("cloud_color", Color("1a1c2e"))
|
|
cloud_material.set_shader_parameter("shadow_color", Color("0d0e17"))
|
|
cloud_material.set_shader_parameter("rim_color", Color("000000"))
|
|
|