OverviewFilesCommitsIssuesDocsReleasesPeers

hyper-tps / addons / sky3d_extended / src / cloud_layers / shadow_layer.gd

116 lines · gd ref:
## Cloud shadow layer — ground-level plane that receives 3D noise textures
## and cloud params from SkyDome so shadows match volumetric clouds exactly.
class_name ShadowLayer
extends CloudLayer

const SHADOW_SHADER = preload("res://addons/sky3d_extended/shaders/cloud_shadow_ground.gdshader")

enum ShadowMode { ANALYTICAL, DECAL, BOTH, NONE }

var _mesh: MeshInstance3D
var _material: ShaderMaterial
var shadow_mode: ShadowMode = ShadowMode.ANALYTICAL
var shadow_darkness: float = 0.7


func _init() -> void:
	name = "Shadows"


func get_params() -> Array[Dictionary]:
	return [
		{"name": "shadow_darkness", "min": 0.0, "max": 1.0, "step": 0.01, "value": shadow_darkness},
	]


func set_param(param_name: String, value: float) -> void:
	match param_name:
		"shadow_darkness":
			shadow_darkness = value
			if _material:
				_material.set_shader_parameter("shadow_darkness", value)


func get_param(param_name: String) -> float:
	match param_name:
		"shadow_darkness": return shadow_darkness
	return 0.0


func create(parent: Node3D) -> void:
	_mesh = MeshInstance3D.new()
	_mesh.name = "CloudShadowPlane"
	var plane := PlaneMesh.new()
	plane.size = Vector2(10000, 10000)
	plane.subdivide_width = 4
	plane.subdivide_depth = 4
	_mesh.mesh = plane
	_mesh.cast_shadow = GeometryInstance3D.SHADOW_CASTING_SETTING_OFF
	_material = ShaderMaterial.new()
	_material.shader = SHADOW_SHADER
	_material.render_priority = -3
	_mesh.material_override = _material
	_mesh.position.y = 1.0
	_mesh.visible = shadow_mode in [ShadowMode.DECAL, ShadowMode.BOTH]
	parent.add_child(_mesh)
	_material.set_shader_parameter("shadow_darkness", shadow_darkness)


func destroy() -> void:
	if is_instance_valid(_mesh):
		_mesh.queue_free()
	_mesh = null
	_material = null


func update(shared: CloudSharedState, _delta: float) -> void:
	if not _mesh:
		return
	_mesh.visible = shadow_mode in [ShadowMode.DECAL, ShadowMode.BOTH]
	# Follow camera horizontally at ground level
	_mesh.global_position = Vector3(
		shared.camera_pos.x,
		1.0,
		shared.camera_pos.z
	)
	if _material:
		_material.set_shader_parameter("sun_direction", shared.sun_direction)


func update_lighting(_shared: CloudSharedState) -> void:
	pass


func set_visible(value: bool) -> void:
	if _mesh:
		_mesh.visible = value and shadow_mode in [ShadowMode.DECAL, ShadowMode.BOTH]


func set_shadow_mode(mode: ShadowMode) -> void:
	shadow_mode = mode
	if _mesh:
		_mesh.visible = shadow_mode in [ShadowMode.DECAL, ShadowMode.BOTH]


## Return the ShaderMaterial so SkyDome can push noise/cloud params directly.
func get_material() -> ShaderMaterial:
	return _material


## Get params dict for analytical shadow uniforms on other materials.
func get_cloud_shadow_params(wind_offset: Vector2, weather: float) -> Dictionary:
	return {
		"cloud_shadow_altitude": (1500.0 + 3500.0) * 0.5,
		"cloud_shadow_scale": 1.0 / 10000.0,
		"cloud_shadow_wind_offset": 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, wind_offset: Vector2, weather: float) -> void:
	var params := get_cloud_shadow_params(wind_offset, weather)
	for key in params:
		mat.set_shader_parameter(key, params[key])