OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / addons / sky3d_extended / src / CloudSystem.gd

193 lines · gd ref:
@tool
class_name CloudSystem
extends Node3D

## Thin manager for pluggable cloud layers.
## Holds shared state (sun, moon, wind, weather, camera) and delegates to layers.

## Weather control (0.0 = clear cumulus, 1.0 = storm CB)
@export_range(0.0, 1.0) var weather: float = 0.3 :
	set(value):
		weather = value
		_shared.weather = value
		# Propagate to spatial plane layer if present
		var main_layer := get_spatial_plane_layer()
		if main_layer:
			main_layer.set_param("weather", value)

## Wind
@export var wind_direction: Vector2 = Vector2(1, 0)
@export var wind_speed: float = 0.02

## Cloud formation type (forwarded to SpatialPlaneLayer)
enum CloudType { CUMULUS, STRATUS, STRATOCUMULUS, CUMULONIMBUS, ALTOCUMULUS, CIRROSTRATUS }
@export var cloud_type: CloudType = CloudType.CUMULUS :
	set(value):
		cloud_type = value
		var main_layer := get_spatial_plane_layer()
		if main_layer:
			main_layer.apply_cloud_type_preset(value as SpatialPlaneLayer.CloudType)

## Shadow mode (forwarded to ShadowLayer)
enum ShadowMode { ANALYTICAL, DECAL, BOTH, NONE }
@export var shadow_mode: ShadowMode = ShadowMode.DECAL :
	set(value):
		shadow_mode = value
		var shadow := get_shadow_layer()
		if shadow:
			shadow.set_shadow_mode(value as ShadowLayer.ShadowMode)

@export_range(0.0, 1.0) var shadow_darkness: float = 0.7 :
	set(value):
		shadow_darkness = value
		var shadow := get_shadow_layer()
		if shadow:
			shadow.set_param("shadow_darkness", value)

## Cloud texture scale
@export_range(0.5, 10.0) var cloud_scale: float = 3.0 :
	set(value):
		cloud_scale = value
		var main_layer := get_spatial_plane_layer()
		if main_layer:
			main_layer.set_param("cloud_scale", value)

## Cirrus
@export var cirrus_visible: bool = true :
	set(value):
		cirrus_visible = value
		var cirrus := get_cirrus_layer()
		if cirrus:
			cirrus.enabled = value

@export_range(0.0, 1.0) var cirrus_coverage: float = 0.55 :
	set(value):
		cirrus_coverage = value
		var cirrus := get_cirrus_layer()
		if cirrus:
			cirrus.set_param("coverage", value)

## Special types
@export var altocumulus_enabled: bool = false
@export var mammatus_enabled: bool = false

## Altitudes
@export var main_cloud_altitude: float = 1500.0
@export var cirrus_altitude: float = 3000.0

## Layer registry
var _layers: Array[CloudLayer] = []
var _shared := CloudSharedState.new()


func _ready() -> void:
	# Volumetric clouds now render in the sky shader (SkyMaterial.gdshader).
	# CloudSystem only manages the shadow layer for ground shadow projection.
	var shadow := ShadowLayer.new()
	shadow.shadow_mode = shadow_mode as ShadowLayer.ShadowMode
	shadow.shadow_darkness = shadow_darkness
	add_layer(shadow)


func _process(delta: float) -> void:
	_update_shared_state(delta)
	for layer in _layers:
		if layer.enabled:
			layer.set_visible(true)
			layer.update(_shared, delta)
			layer.update_lighting(_shared)
		else:
			layer.set_visible(false)


## Add a cloud layer to the system.
func add_layer(layer: CloudLayer) -> void:
	_layers.append(layer)
	layer.create(self)


## Remove a cloud layer from the system.
func remove_layer(layer: CloudLayer) -> void:
	layer.destroy()
	_layers.erase(layer)


## Get all registered layers.
func get_layers() -> Array[CloudLayer]:
	return _layers


## Get all layer parameters for debug panel auto-discovery.
func get_all_layer_params() -> Array[Dictionary]:
	var result: Array[Dictionary] = []
	for layer in _layers:
		result.append({
			"layer_name": layer.name,
			"enabled": layer.enabled,
			"params": layer.get_params(),
		})
	return result


## Find the SpatialPlaneLayer (main cloud layer).
func get_spatial_plane_layer() -> SpatialPlaneLayer:
	for layer in _layers:
		if layer is SpatialPlaneLayer:
			return layer as SpatialPlaneLayer
	return null


## Find the CirrusLayer.
func get_cirrus_layer() -> CirrusLayer:
	for layer in _layers:
		if layer is CirrusLayer:
			return layer as CirrusLayer
	return null


## Find the ShadowLayer.
func get_shadow_layer() -> ShadowLayer:
	for layer in _layers:
		if layer is ShadowLayer:
			return layer as ShadowLayer
	return null


## Get params dict for analytical shadow uniforms on other materials.
func get_cloud_shadow_params() -> Dictionary:
	var shadow := get_shadow_layer()
	if shadow:
		return shadow.get_cloud_shadow_params(_shared.wind_offset, weather)
	return {
		"cloud_shadow_altitude": main_cloud_altitude,
		"cloud_shadow_scale": 1.0 / 10000.0,
		"cloud_shadow_wind_offset": _shared.wind_offset,
		"cloud_shadow_coverage": lerpf(0.25, 0.95, weather),
		"cloud_shadow_darkness": shadow_darkness,
	}


## Apply cloud shadow uniforms to a ShaderMaterial.
func apply_cloud_shadow_params(mat: ShaderMaterial) -> void:
	var params := get_cloud_shadow_params()
	for key in params:
		mat.set_shader_parameter(key, params[key])


func _update_shared_state(delta: float) -> void:
	_shared.weather = weather
	var cam := get_viewport().get_camera_3d()
	_shared.camera_pos = cam.global_position if cam else Vector3.ZERO

	var sky3d: Sky3DExtended = get_parent() as Sky3DExtended
	if sky3d and sky3d.sun:
		_shared.sun_direction = -sky3d.sun.global_transform.basis.z
		var col: Color = sky3d.sun.light_color
		_shared.sun_color = Vector3(col.r, col.g, col.b)
		_shared.sun_energy = sky3d.sun.light_energy
	if sky3d and sky3d.moon:
		_shared.moon_direction = -sky3d.moon.global_transform.basis.z
		_shared.moon_energy = sky3d.moon.light_energy
	if sky3d and sky3d.tod:
		_shared.time_of_day = sky3d.tod.current_time